* first attempt at stream sidebar, tasks, and events * fix folder from root * cleanup folders and events, and condense changes * cleanup and add toggle for sidebar * cleanup * fix nits
126 lines
3.9 KiB
TypeScript
126 lines
3.9 KiB
TypeScript
import { useCallback, useState } from 'react';
|
|
import type { TaskProperties } from '@/api/types';
|
|
import { useNetwork } from '@/hooks/use-networks';
|
|
import { metaKey } from '@/lib/platform';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Textarea } from '@/components/ui/textarea';
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from '@/components/ui/select';
|
|
import { KeyHint } from '@/components/key-hint';
|
|
|
|
const UNASSIGNED = 'unassigned';
|
|
|
|
interface TaskComposeStepProps {
|
|
networkId: string;
|
|
onCancel: () => void;
|
|
onSubmit: (properties: TaskProperties) => void;
|
|
}
|
|
|
|
/**
|
|
* Minimal task creation form. Checklist items are added after creation in
|
|
* the always-editable task view, keeping this step a quick capture.
|
|
*/
|
|
export function TaskComposeStep({
|
|
networkId,
|
|
onCancel,
|
|
onSubmit,
|
|
}: TaskComposeStepProps) {
|
|
const network = useNetwork(networkId);
|
|
const [title, setTitle] = useState('');
|
|
const [notes, setNotes] = useState('');
|
|
const [assignedTo, setAssignedTo] = useState<string>(UNASSIGNED);
|
|
|
|
const handleSubmit = useCallback(() => {
|
|
const trimmed = title.trim();
|
|
if (!trimmed) return;
|
|
onSubmit({
|
|
title: trimmed,
|
|
...(notes.trim() && { notes: notes.trim() }),
|
|
...(assignedTo !== UNASSIGNED && { assigned_to: assignedTo }),
|
|
checklist: [],
|
|
done: false,
|
|
});
|
|
}, [title, notes, assignedTo, onSubmit]);
|
|
|
|
const handleKeyDown = useCallback(
|
|
(e: React.KeyboardEvent) => {
|
|
if (e.key === 'Escape') {
|
|
e.preventDefault();
|
|
onCancel();
|
|
} else if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
|
e.preventDefault();
|
|
handleSubmit();
|
|
}
|
|
},
|
|
[onCancel, handleSubmit],
|
|
);
|
|
|
|
return (
|
|
<div
|
|
className="absolute inset-0 z-50 flex flex-col bg-black/90 pt-16"
|
|
onKeyDown={handleKeyDown}
|
|
tabIndex={-1}
|
|
>
|
|
<div className="mx-auto w-full max-w-sm space-y-4 px-6">
|
|
<div>
|
|
<Label className="mb-1 text-xs text-white/50">Task</Label>
|
|
<Input
|
|
type="text"
|
|
autoFocus
|
|
value={title}
|
|
onChange={(e) => setTitle(e.target.value)}
|
|
placeholder="What needs to get done?"
|
|
className="border-white/10 bg-white/5 text-white placeholder-white/30 focus-visible:border-white/30 focus-visible:ring-0"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<Label className="mb-1 text-xs text-white/50">Notes</Label>
|
|
<Textarea
|
|
value={notes}
|
|
onChange={(e) => setNotes(e.target.value)}
|
|
placeholder="Optional details…"
|
|
className="min-h-20 border-white/10 bg-white/5 text-white placeholder:text-white/30 focus-visible:border-white/30 focus-visible:ring-0 dark:bg-white/5"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<Label className="mb-1 text-xs text-white/50">Assign to</Label>
|
|
<Select value={assignedTo} onValueChange={setAssignedTo}>
|
|
<SelectTrigger className="w-full border-white/10 bg-white/5 text-white">
|
|
<SelectValue placeholder="Unassigned" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value={UNASSIGNED}>Unassigned</SelectItem>
|
|
{network?.humans?.map((human) => (
|
|
<SelectItem key={human.id} value={human.id}>
|
|
{human.email_prefix}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="absolute bottom-8 left-0 right-0 flex items-center justify-center gap-4 text-sm text-white/50">
|
|
<KeyHint keys="Esc" onClick={onCancel} title="Cancel (or press Esc)">
|
|
cancel
|
|
</KeyHint>
|
|
<KeyHint
|
|
keys={`${metaKey}+Enter`}
|
|
onClick={handleSubmit}
|
|
title={`Create task (or press ${metaKey}+Enter)`}
|
|
>
|
|
create
|
|
</KeyHint>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|