171 lines
7.0 KiB
React
171 lines
7.0 KiB
React
import React, { useState, useEffect, useRef } from 'react';
|
|
|
|
const COLOURS = ['#3d3a79', '#2d6a4f', '#7b2d8b', '#c0392b', '#d4800a', '#1a6b8a'];
|
|
|
|
export default function ProjectModal({ project, mode, onSave, onDelete, onClose }) {
|
|
const [name, setName] = useState(project?.name ?? '');
|
|
const [description, setDescription] = useState(project?.description ?? '');
|
|
const [colour, setColour] = useState(project?.colour ?? COLOURS[0]);
|
|
const [isolated, setIsolated] = useState(project?.isolated === 1);
|
|
const inputRef = useRef(null);
|
|
|
|
useEffect(() => {
|
|
if (mode !== 'confirm-delete') inputRef.current?.focus();
|
|
}, [mode]);
|
|
|
|
function handleSubmit() {
|
|
const trimmed = name.trim();
|
|
if (!trimmed) return;
|
|
onSave({ name: trimmed, description: description.trim() || null, colour, icon: null, isolated });
|
|
onClose();
|
|
}
|
|
|
|
function handleKeyDown(e) {
|
|
if (e.key === 'Enter' && mode !== 'confirm-delete') handleSubmit();
|
|
if (e.key === 'Escape') onClose();
|
|
}
|
|
|
|
return (
|
|
<div onClick={onClose} style={{
|
|
position: 'fixed', inset: 0,
|
|
background: 'rgba(0,0,0,0.5)',
|
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
zIndex: 100,
|
|
}}>
|
|
<div onClick={e => e.stopPropagation()} onKeyDown={handleKeyDown} style={{
|
|
background: 'var(--bg-surface)',
|
|
border: '1px solid var(--border)',
|
|
borderRadius: 'var(--radius-lg)',
|
|
padding: '24px', width: '380px',
|
|
display: 'flex', flexDirection: 'column', gap: '16px',
|
|
}}>
|
|
{mode === 'confirm-delete' ? (
|
|
<>
|
|
<h2 style={{ fontSize: '15px', fontWeight: 600, color: 'var(--text-primary)' }}>
|
|
Delete project?
|
|
</h2>
|
|
<p className="text-sm text-secondary">
|
|
Are you sure you want to delete{' '}
|
|
<span style={{ color: 'var(--text-primary)', fontWeight: 500 }}>{project.name}</span>?
|
|
Sessions in this project will not be deleted.
|
|
</p>
|
|
<div className="flex" style={{ gap: '8px', justifyContent: 'flex-end' }}>
|
|
<button className="btn-reset text-base text-muted"
|
|
onClick={onClose}
|
|
style={{ padding: '8px 14px', borderRadius: 'var(--radius-md)' }}>
|
|
Cancel
|
|
</button>
|
|
<button className="btn-reset text-base"
|
|
onClick={() => { onDelete(project.id); onClose(); }}
|
|
style={{ padding: '8px 16px', borderRadius: 'var(--radius-md)', background: '#c0392b', color: 'white' }}>
|
|
Delete
|
|
</button>
|
|
</div>
|
|
</>
|
|
) : (
|
|
<>
|
|
<h2 style={{ fontSize: '15px', fontWeight: 600, color: 'var(--text-primary)' }}>
|
|
{mode === 'create' ? 'New Project' : 'Edit Project'}
|
|
</h2>
|
|
|
|
{/* Name */}
|
|
<div className="flex-col" style={{ gap: '6px' }}>
|
|
<label className="label-upper">Name</label>
|
|
<input
|
|
ref={inputRef}
|
|
value={name}
|
|
onChange={e => setName(e.target.value)}
|
|
placeholder="Project name..."
|
|
style={{
|
|
background: 'var(--bg-elevated)', border: '1px solid var(--border)',
|
|
borderRadius: 'var(--radius-md)', padding: '8px 12px',
|
|
color: 'var(--text-primary)', fontSize: '14px', outline: 'none', width: '100%',
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
{/* Description */}
|
|
<div className="flex-col" style={{ gap: '6px' }}>
|
|
<label className="label-upper">Description <span style={{ opacity: 0.5 }}>(optional)</span></label>
|
|
<textarea
|
|
value={description}
|
|
onChange={e => setDescription(e.target.value)}
|
|
placeholder="What's this project about..."
|
|
rows={2}
|
|
style={{
|
|
background: 'var(--bg-elevated)', border: '1px solid var(--border)',
|
|
borderRadius: 'var(--radius-md)', padding: '8px 12px',
|
|
color: 'var(--text-primary)', fontSize: '14px', outline: 'none',
|
|
width: '100%', resize: 'none', fontFamily: 'inherit',
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
{/* Colour picker */}
|
|
<div className="flex-col" style={{ gap: '6px' }}>
|
|
<label className="label-upper">Colour</label>
|
|
<div className="flex" style={{ gap: '8px' }}>
|
|
{COLOURS.map(c => (
|
|
<button
|
|
key={c}
|
|
onClick={() => setColour(c)}
|
|
className="btn-reset"
|
|
style={{
|
|
width: '24px', height: '24px',
|
|
borderRadius: '50%',
|
|
background: c,
|
|
border: colour === c ? '2px solid var(--text-primary)' : '2px solid transparent',
|
|
outline: colour === c ? '2px solid var(--accent-hover)' : 'none',
|
|
outlineOffset: '2px',
|
|
}}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Project-Only toggle */}
|
|
<div className="flex items-center" style={{ justifyContent: 'space-between' }}>
|
|
<div className="flex-col" style={{ gap: '2px' }}>
|
|
<label className="label-upper">Project Only</label>
|
|
<span className="text-xs text-muted">Limit context to this project only</span>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={() => setIsolated(i => !i)}
|
|
style={{
|
|
width: '40px', height: '22px', borderRadius: '11px', flexShrink: 0,
|
|
background: isolated ? 'var(--accent-hover)' : 'var(--bg-elevated)',
|
|
border: '1px solid var(--border)',
|
|
position: 'relative', cursor: 'pointer', transition: 'background 0.2s',
|
|
}}
|
|
>
|
|
<span style={{
|
|
position: 'absolute', top: '3px',
|
|
left: isolated ? '20px' : '3px',
|
|
width: '14px', height: '14px',
|
|
borderRadius: '50%',
|
|
background: isolated ? 'white' : 'var(--text-muted)',
|
|
transition: 'left 0.2s',
|
|
}} />
|
|
</button>
|
|
</div>
|
|
|
|
<div className="flex" style={{ gap: '8px', justifyContent: 'flex-end' }}>
|
|
<button className="btn-reset text-base text-muted"
|
|
onClick={onClose}
|
|
style={{ padding: '8px 14px', borderRadius: 'var(--radius-md)' }}>
|
|
Cancel
|
|
</button>
|
|
<button className="btn-primary"
|
|
onClick={handleSubmit}
|
|
disabled={!name.trim()}
|
|
style={{ padding: '8px 16px' }}>
|
|
{mode === 'create' ? 'Create' : 'Save'}
|
|
</button>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
} |