Kanban: Modal-Scroll auf Mobile (Buttons immer sichtbar), Erstellungsdatum auf Karte
This commit is contained in:
@@ -9,7 +9,13 @@ const PRIORITIES = [
|
||||
{ id: 'high', label: 'Hoch', color: '#ff6b9d' },
|
||||
];
|
||||
const COL_COLORS = ['#4ecdc4','#60a5fa','#a78bfa','#ff6b9d','#ffe66d','#fb923c','#4ade80','#f472b6','#94a3b8'];
|
||||
const prioOf = id => PRIORITIES.find(p => p.id === id) || PRIORITIES[0];
|
||||
const fmtCardDate = s => {
|
||||
if (!s) return '';
|
||||
const d = new Date(String(s).replace(' ', 'T'));
|
||||
if (isNaN(d)) return '';
|
||||
return d.toLocaleDateString('de-DE', { day:'2-digit', month:'2-digit', year:'numeric' })
|
||||
+ ' ' + d.toLocaleTimeString('de-DE', { hour:'2-digit', minute:'2-digit' });
|
||||
};
|
||||
|
||||
// ── Karten-Modal ──────────────────────────────────────────────────────────────
|
||||
function CardModal({ card, columnId, columns, onSave, onClose, toast }) {
|
||||
@@ -64,64 +70,74 @@ function CardModal({ card, columnId, columns, onSave, onClose, toast }) {
|
||||
style={{ position:'fixed',inset:0,background:'rgba(0,0,0,0.7)',zIndex:1000,
|
||||
display:'flex',alignItems:isMob?'flex-end':'center',justifyContent:'center',padding:isMob?0:20 }}>
|
||||
<div style={{ background:'#1a1a1e',border:'1px solid rgba(255,255,255,0.12)',
|
||||
borderRadius:isMob?'16px 16px 0 0':14,padding:'22px 20px 28px',
|
||||
width:'100%',maxWidth:460 }}>
|
||||
{isMob && <div style={{ width:36,height:4,background:'rgba(255,255,255,0.15)',borderRadius:2,margin:'0 auto 16px' }}/>}
|
||||
|
||||
<div style={{ display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:16 }}>
|
||||
<div style={{ ...S.head, marginBottom:0 }}>{card ? 'KARTE BEARBEITEN' : 'NEUE KARTE'}</div>
|
||||
<button onClick={onClose} style={{ background:'transparent',border:'none',color:'rgba(255,255,255,0.4)',cursor:'pointer',fontSize:18 }}>✕</button>
|
||||
</div>
|
||||
|
||||
<input ref={titleRef} value={title} onChange={e=>setTitle(e.target.value)} onKeyDown={onKey}
|
||||
placeholder="Titel" style={{ ...S.inp, marginBottom:10 }} />
|
||||
|
||||
<textarea value={desc} onChange={e=>setDesc(e.target.value)} onKeyDown={onKey}
|
||||
placeholder="Beschreibung (optional)" rows={3}
|
||||
style={{ ...S.inp, resize:'vertical', marginBottom:14 }} />
|
||||
|
||||
{/* Priorität */}
|
||||
<div style={{ marginBottom:14 }}>
|
||||
<div style={{ ...S.head, marginBottom:8 }}>PRIORITÄT</div>
|
||||
<div style={{ display:'flex', gap:6 }}>
|
||||
{PRIORITIES.map(p => (
|
||||
<button key={p.id} onClick={()=>setPrio(p.id)} style={{
|
||||
...S.btn(p.color, true),
|
||||
background: prio===p.id ? `${p.color}28` : `${p.color}0e`,
|
||||
borderColor: prio===p.id ? p.color : `${p.color}35`,
|
||||
fontWeight: prio===p.id ? 700 : 400,
|
||||
}}>{p.label}</button>
|
||||
))}
|
||||
borderRadius:isMob?'16px 16px 0 0':14,
|
||||
width:'100%',maxWidth:460,
|
||||
display:'flex',flexDirection:'column',
|
||||
maxHeight: isMob ? '92vh' : '90vh',
|
||||
}}>
|
||||
{/* Drag-Handle + Header – immer sichtbar, nicht scrollend */}
|
||||
<div style={{ padding:'18px 20px 0', flexShrink:0 }}>
|
||||
{isMob && <div style={{ width:36,height:4,background:'rgba(255,255,255,0.15)',borderRadius:2,margin:'0 auto 14px' }}/>}
|
||||
<div style={{ display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:16 }}>
|
||||
<div style={{ ...S.head, marginBottom:0 }}>{card ? 'KARTE BEARBEITEN' : 'NEUE KARTE'}</div>
|
||||
<button onClick={onClose} style={{ background:'transparent',border:'none',color:'rgba(255,255,255,0.4)',cursor:'pointer',fontSize:18 }}>✕</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Verschieben nach (nur bei Bearbeitung sinnvoll, aber auch bei Neu nützlich) */}
|
||||
<div style={{ marginBottom:18 }}>
|
||||
<div style={{ ...S.head, marginBottom:8 }}>{card ? 'VERSCHIEBEN NACH' : 'IN SPALTE'}</div>
|
||||
{!showNewCol ? (
|
||||
{/* Scrollbarer Inhalt */}
|
||||
<div style={{ overflowY:'auto',padding:'0 20px',flex:1,scrollbarWidth:'thin',scrollbarColor:'rgba(255,255,255,0.08) transparent' }}>
|
||||
<input ref={titleRef} value={title} onChange={e=>setTitle(e.target.value)} onKeyDown={onKey}
|
||||
placeholder="Titel" style={{ ...S.inp, marginBottom:10 }} />
|
||||
|
||||
<textarea value={desc} onChange={e=>setDesc(e.target.value)} onKeyDown={onKey}
|
||||
placeholder="Beschreibung (optional)" rows={3}
|
||||
style={{ ...S.inp, resize:'vertical', marginBottom:14 }} />
|
||||
|
||||
{/* Priorität */}
|
||||
<div style={{ marginBottom:14 }}>
|
||||
<div style={{ ...S.head, marginBottom:8 }}>PRIORITÄT</div>
|
||||
<div style={{ display:'flex', gap:6, flexWrap:'wrap' }}>
|
||||
{columns.map(c => (
|
||||
<button key={c.id} onClick={()=>setMoveToCol(c.id)} style={{
|
||||
...S.btn(c.color || '#4ecdc4', true),
|
||||
background: moveToCol===c.id ? `${c.color||'#4ecdc4'}28` : `${c.color||'#4ecdc4'}0e`,
|
||||
borderColor: moveToCol===c.id ? (c.color||'#4ecdc4') : `${c.color||'#4ecdc4'}35`,
|
||||
fontWeight: moveToCol===c.id ? 700 : 400,
|
||||
}}>{c.title}</button>
|
||||
{PRIORITIES.map(p => (
|
||||
<button key={p.id} onClick={()=>setPrio(p.id)} style={{
|
||||
...S.btn(p.color, true),
|
||||
background: prio===p.id ? `${p.color}28` : `${p.color}0e`,
|
||||
borderColor: prio===p.id ? p.color : `${p.color}35`,
|
||||
fontWeight: prio===p.id ? 700 : 400,
|
||||
}}>{p.label}</button>
|
||||
))}
|
||||
<button onClick={()=>setShowNewCol(true)} style={S.btn('#888888', true)}>+ Neue Spalte</button>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ display:'flex', gap:6 }}>
|
||||
<input ref={newColRef} value={newColName} onChange={e=>setNewColName(e.target.value)}
|
||||
onKeyDown={e=>{ if(e.key==='Escape'){setShowNewCol(false);setNewColName('');} }}
|
||||
placeholder="Name der neuen Spalte"
|
||||
style={{ ...S.inp, flex:1, fontSize:12 }} />
|
||||
<button onClick={()=>{setShowNewCol(false);setNewColName('');}} style={S.btn('#888888',true)}>✕</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Verschieben nach */}
|
||||
<div style={{ marginBottom:14 }}>
|
||||
<div style={{ ...S.head, marginBottom:8 }}>{card ? 'VERSCHIEBEN NACH' : 'IN SPALTE'}</div>
|
||||
{!showNewCol ? (
|
||||
<div style={{ display:'flex', gap:6, flexWrap:'wrap' }}>
|
||||
{columns.map(c => (
|
||||
<button key={c.id} onClick={()=>setMoveToCol(c.id)} style={{
|
||||
...S.btn(c.color || '#4ecdc4', true),
|
||||
background: moveToCol===c.id ? `${c.color||'#4ecdc4'}28` : `${c.color||'#4ecdc4'}0e`,
|
||||
borderColor: moveToCol===c.id ? (c.color||'#4ecdc4') : `${c.color||'#4ecdc4'}35`,
|
||||
fontWeight: moveToCol===c.id ? 700 : 400,
|
||||
}}>{c.title}</button>
|
||||
))}
|
||||
<button onClick={()=>setShowNewCol(true)} style={S.btn('#888888', true)}>+ Neue Spalte</button>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ display:'flex', gap:6 }}>
|
||||
<input ref={newColRef} value={newColName} onChange={e=>setNewColName(e.target.value)}
|
||||
onKeyDown={e=>{ if(e.key==='Escape'){setShowNewCol(false);setNewColName('');} }}
|
||||
placeholder="Name der neuen Spalte"
|
||||
style={{ ...S.inp, flex:1, fontSize:12 }} />
|
||||
<button onClick={()=>{setShowNewCol(false);setNewColName('');}} style={S.btn('#888888',true)}>✕</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display:'flex', gap:8, justifyContent:'flex-end' }}>
|
||||
{/* Aktionsbuttons – immer sichtbar am unteren Rand, nicht scrollend */}
|
||||
<div style={{ padding:'14px 20px 24px', flexShrink:0, borderTop:'1px solid rgba(255,255,255,0.07)',
|
||||
display:'flex', gap:8, justifyContent:'flex-end' }}>
|
||||
<button onClick={onClose} style={S.btn('#888888', true)}>Abbrechen</button>
|
||||
<button onClick={save} disabled={!title.trim()||saving} style={S.btn('#4ecdc4', true)}>
|
||||
{saving ? '…' : card ? 'Speichern' : 'Erstellen'}
|
||||
@@ -274,6 +290,14 @@ function KanbanCard({ card, col, allCols, onEdit, onDelete, onMove, isDragging,
|
||||
{arrowBtn('→', isLastCol, ()=>onMove(card.id, allCols[colIdx+1]?.id))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Erstellungsdatum */}
|
||||
{card.created_at && (
|
||||
<div style={{ fontSize:9,fontFamily:'monospace',color:'rgba(255,255,255,0.2)',
|
||||
marginTop:6, textAlign:'right', letterSpacing:0.5 }}>
|
||||
{fmtCardDate(card.created_at)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user