/** @jsxImportSource react */ import { useState } from 'react'; import type { ReactElement } from 'react'; import styled from 'styled-components'; import { TaskPriority, EnergyLevel, PROJECT_TYPE_TABS } from '@life-platform/shared'; import type { Project, CreateTaskDto } from '@life-platform/shared'; import { useCreateTask } from '@features/tasks/frontend/useTasks'; import { useSprints } from '@features/projects/frontend/useProjects'; // --------------------------------------------------------------------------- // Styled (matching CreateHabitModal pattern) // --------------------------------------------------------------------------- const ModalOverlay = styled.div` position: fixed; inset: 0; background: #000000aa; display: flex; align-items: center; justify-content: center; z-index: 100; `; const ModalBox = styled.div` background: ${({ theme }) => theme.colors.surface}; border: 1px solid ${({ theme }) => theme.colors.border.default}; border-radius: 8px; padding: 24px; width: 100%; max-width: 480px; max-height: 85vh; overflow-y: auto; display: flex; flex-direction: column; gap: 14px; `; const ModalTitle = styled.h2` font-size: 16px; font-weight: 700; color: ${({ theme }) => theme.colors.text.primary}; margin: 0; font-family: monospace; `; const FormField = styled.div` display: flex; flex-direction: column; gap: 6px; `; const Label = styled.label` font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; color: ${({ theme }) => theme.colors.text.muted}; `; const StyledInput = styled.input` background: ${({ theme }) => theme.colors.background.primary}; border: 1px solid ${({ theme }) => theme.colors.border.default}; border-radius: 4px; padding: 8px 12px; font-size: 13px; color: ${({ theme }) => theme.colors.text.primary}; outline: none; &:focus { border-color: ${({ theme }) => theme.colors.primary.main}; } `; const StyledSelect = styled.select` background: ${({ theme }) => theme.colors.background.primary}; border: 1px solid ${({ theme }) => theme.colors.border.default}; border-radius: 4px; padding: 8px 12px; font-size: 13px; color: ${({ theme }) => theme.colors.text.primary}; outline: none; cursor: pointer; &:focus { border-color: ${({ theme }) => theme.colors.primary.main}; } `; const StyledTextarea = styled.textarea` background: ${({ theme }) => theme.colors.background.primary}; border: 1px solid ${({ theme }) => theme.colors.border.default}; border-radius: 4px; padding: 8px 12px; font-size: 13px; color: ${({ theme }) => theme.colors.text.primary}; outline: none; resize: vertical; min-height: 60px; &:focus { border-color: ${({ theme }) => theme.colors.primary.main}; } `; const ChipRow = styled.div` display: flex; gap: 6px; flex-wrap: wrap; `; const Chip = styled.button<{ active?: boolean; accentColor?: string }>` padding: 4px 10px; border-radius: 12px; font-size: 11px; font-family: monospace; cursor: pointer; transition: all 0.15s ease; background: ${({ active, accentColor, theme }) => active ? (accentColor ?? theme.colors.primary.main) + '22' : 'transparent'}; border: 1px solid ${({ active, accentColor, theme }) => active ? (accentColor ?? theme.colors.primary.main) : theme.colors.border.default}; color: ${({ active, accentColor, theme }) => active ? (accentColor ?? theme.colors.primary.main) : theme.colors.text.secondary}; &:hover { border-color: ${({ accentColor, theme }) => accentColor ?? theme.colors.primary.main}; } `; const InlineRow = styled.div` display: flex; gap: 12px; align-items: flex-end; `; const CheckboxLabel = styled.label` display: flex; align-items: center; gap: 8px; font-size: 12px; color: ${({ theme }) => theme.colors.text.secondary}; cursor: pointer; input { accent-color: ${({ theme }) => theme.colors.primary.main}; } `; const ModalActions = styled.div` display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; `; const CancelButton = styled.button` padding: 8px 16px; background: transparent; border: 1px solid ${({ theme }) => theme.colors.border.default}; border-radius: 4px; color: ${({ theme }) => theme.colors.text.secondary}; font-size: 12px; cursor: pointer; &:hover { border-color: ${({ theme }) => theme.colors.text.muted}; } `; const SubmitButton = styled.button` padding: 8px 16px; background: color-mix(in srgb, ${({ theme }) => theme.colors.primary.main} 13%, transparent); border: 1px solid ${({ theme }) => theme.colors.primary.main}; border-radius: 4px; color: ${({ theme }) => theme.colors.primary.main}; font-size: 12px; font-family: monospace; cursor: pointer; &:hover { background: color-mix(in srgb, ${({ theme }) => theme.colors.primary.main} 20%, transparent); } &:disabled { opacity: 0.4; cursor: not-allowed; } `; // --------------------------------------------------------------------------- // Priority colors // --------------------------------------------------------------------------- const PRIORITY_COLORS: Record = { [TaskPriority.Critical]: '#ff0040', [TaskPriority.High]: '#ff6b00', [TaskPriority.Medium]: '#ffaa00', [TaskPriority.Low]: '#555', }; const ENERGY_COLORS: Record = { [EnergyLevel.High]: '#ff6b00', [EnergyLevel.Medium]: '#ffaa00', [EnergyLevel.Low]: '#00aaff', }; // --------------------------------------------------------------------------- // Component // --------------------------------------------------------------------------- export interface CreateTaskModalProps { projects: Project[]; onClose: () => void; } export function CreateTaskModal({ projects, onClose }: CreateTaskModalProps): ReactElement { const createTask = useCreateTask(); const [form, setForm] = useState({ projectId: projects[0]?.id ?? '', title: '', description: '', priority: TaskPriority.Medium, energyLevel: EnergyLevel.Medium, dueDate: '', estimatedMinutes: undefined, isQuickWin: false, }); const selectedProject = projects.find((p) => p.id === form.projectId); const projectHasSprints = selectedProject && PROJECT_TYPE_TABS[selectedProject.type]?.includes('sprints'); const { data: sprintsResult } = useSprints({ projectId: form.projectId, status: 'active,planned' }); const sprints = sprintsResult?.data ?? []; function setField(key: K, value: CreateTaskDto[K]): void { setForm((prev) => ({ ...prev, [key]: value })); } function handleSubmit(): void { if (!form.title.trim() || !form.projectId) return; const payload: CreateTaskDto = { ...form, title: form.title.trim(), description: form.description?.trim() || undefined, dueDate: form.dueDate || undefined, estimatedMinutes: form.estimatedMinutes || undefined, sprintId: form.sprintId || undefined, }; createTask.mutate(payload, { onSuccess: onClose }); } return ( e.target === e.currentTarget && onClose()}> New Task setField('title', e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleSubmit()} autoFocus /> setForm((prev) => ({ ...prev, projectId: e.target.value, sprintId: undefined }))} > {projects.map((p) => ( ))} {projectHasSprints && ( setField('sprintId', e.target.value || undefined)} > {sprints.map((s) => ( ))} )} {([TaskPriority.Low, TaskPriority.Medium, TaskPriority.High, TaskPriority.Critical] as const).map((p) => ( setField('priority', p)} > {p} ))} {([EnergyLevel.Low, EnergyLevel.Medium, EnergyLevel.High] as const).map((e) => ( setField('energyLevel', e)} > {e} ))} setField('dueDate', e.target.value)} /> setField('estimatedMinutes', e.target.value ? Number(e.target.value) : undefined)} /> setField('isQuickWin', e.target.checked)} /> Quick win (under 15 min, low effort) setField('description', e.target.value)} /> Cancel {createTask.isPending ? 'Creating...' : 'Create Task'} ); }