Global enhancements: none priority, optional remind_at, required labels, textarea flex, remove color picker
- Add "none" priority (grey) to task/todo schemas, types, and all priority color maps - Make remind_at optional on reminders (schema, model, migration 010) - Add required prop to Label component with red asterisk indicator - Add invalid:ring-red-500 to Input, Select, Textarea base classes - Mark mandatory fields with required labels across all forms - Replace fixed textarea rows with min-h + flex-1 for auto-expand - Remove color picker from ProjectForm - Align TaskRow metadata into fixed-width columns Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
bfe97fd749
commit
4169c245c2
23
backend/alembic/versions/010_make_remind_at_nullable.py
Normal file
23
backend/alembic/versions/010_make_remind_at_nullable.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
"""make remind_at nullable
|
||||||
|
|
||||||
|
Revision ID: 010
|
||||||
|
Revises: 009
|
||||||
|
Create Date: 2026-02-22
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "010"
|
||||||
|
down_revision = "009"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
op.alter_column("reminders", "remind_at", existing_type=sa.DateTime(), nullable=True)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
op.alter_column("reminders", "remind_at", existing_type=sa.DateTime(), nullable=False)
|
||||||
@ -11,7 +11,7 @@ class Reminder(Base):
|
|||||||
id: Mapped[int] = mapped_column(primary_key=True, index=True)
|
id: Mapped[int] = mapped_column(primary_key=True, index=True)
|
||||||
title: Mapped[str] = mapped_column(String(255), nullable=False)
|
title: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||||
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||||
remind_at: Mapped[datetime] = mapped_column(nullable=False)
|
remind_at: Mapped[Optional[datetime]] = mapped_column(nullable=True)
|
||||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||||
is_dismissed: Mapped[bool] = mapped_column(Boolean, default=False)
|
is_dismissed: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||||
recurrence_rule: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
recurrence_rule: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
||||||
|
|||||||
@ -4,7 +4,7 @@ from typing import Optional, List, Literal
|
|||||||
from app.schemas.task_comment import TaskCommentResponse
|
from app.schemas.task_comment import TaskCommentResponse
|
||||||
|
|
||||||
TaskStatus = Literal["pending", "in_progress", "completed"]
|
TaskStatus = Literal["pending", "in_progress", "completed"]
|
||||||
TaskPriority = Literal["low", "medium", "high"]
|
TaskPriority = Literal["none", "low", "medium", "high"]
|
||||||
|
|
||||||
|
|
||||||
class ProjectTaskCreate(BaseModel):
|
class ProjectTaskCreate(BaseModel):
|
||||||
|
|||||||
@ -6,7 +6,7 @@ from typing import Optional
|
|||||||
class ReminderCreate(BaseModel):
|
class ReminderCreate(BaseModel):
|
||||||
title: str
|
title: str
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
remind_at: datetime
|
remind_at: Optional[datetime] = None
|
||||||
is_active: bool = True
|
is_active: bool = True
|
||||||
recurrence_rule: Optional[str] = None
|
recurrence_rule: Optional[str] = None
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ class ReminderResponse(BaseModel):
|
|||||||
id: int
|
id: int
|
||||||
title: str
|
title: str
|
||||||
description: Optional[str]
|
description: Optional[str]
|
||||||
remind_at: datetime
|
remind_at: Optional[datetime]
|
||||||
is_active: bool
|
is_active: bool
|
||||||
is_dismissed: bool
|
is_dismissed: bool
|
||||||
recurrence_rule: Optional[str]
|
recurrence_rule: Optional[str]
|
||||||
|
|||||||
@ -2,7 +2,7 @@ from pydantic import BaseModel, ConfigDict
|
|||||||
from datetime import datetime, date
|
from datetime import datetime, date
|
||||||
from typing import Optional, Literal
|
from typing import Optional, Literal
|
||||||
|
|
||||||
TodoPriority = Literal["low", "medium", "high"]
|
TodoPriority = Literal["none", "low", "medium", "high"]
|
||||||
|
|
||||||
|
|
||||||
class TodoCreate(BaseModel):
|
class TodoCreate(BaseModel):
|
||||||
|
|||||||
@ -214,7 +214,7 @@ export default function EventForm({ event, initialStart, initialEnd, initialAllD
|
|||||||
<form onSubmit={handleSubmit} className="flex flex-col flex-1 overflow-y-auto">
|
<form onSubmit={handleSubmit} className="flex flex-col flex-1 overflow-y-auto">
|
||||||
<div className="px-6 py-5 space-y-4 flex-1">
|
<div className="px-6 py-5 space-y-4 flex-1">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="title">Title</Label>
|
<Label htmlFor="title" required>Title</Label>
|
||||||
<Input
|
<Input
|
||||||
id="title"
|
id="title"
|
||||||
value={formData.title}
|
value={formData.title}
|
||||||
@ -229,7 +229,7 @@ export default function EventForm({ event, initialStart, initialEnd, initialAllD
|
|||||||
id="description"
|
id="description"
|
||||||
value={formData.description}
|
value={formData.description}
|
||||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||||
rows={4}
|
className="min-h-[80px] flex-1"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -252,7 +252,7 @@ export default function EventForm({ event, initialStart, initialEnd, initialAllD
|
|||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="start">Start</Label>
|
<Label htmlFor="start" required>Start</Label>
|
||||||
<Input
|
<Input
|
||||||
id="start"
|
id="start"
|
||||||
type={formData.all_day ? 'date' : 'datetime-local'}
|
type={formData.all_day ? 'date' : 'datetime-local'}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ const COLUMNS: { id: string; label: string; color: string }[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const priorityColors: Record<string, string> = {
|
const priorityColors: Record<string, string> = {
|
||||||
|
none: 'bg-gray-500/20 text-gray-400',
|
||||||
low: 'bg-green-500/20 text-green-400',
|
low: 'bg-green-500/20 text-green-400',
|
||||||
medium: 'bg-yellow-500/20 text-yellow-400',
|
medium: 'bg-yellow-500/20 text-yellow-400',
|
||||||
high: 'bg-red-500/20 text-red-400',
|
high: 'bg-red-500/20 text-red-400',
|
||||||
|
|||||||
@ -53,7 +53,7 @@ const statusLabels: Record<string, string> = {
|
|||||||
type SortMode = 'manual' | 'priority' | 'due_date';
|
type SortMode = 'manual' | 'priority' | 'due_date';
|
||||||
type ViewMode = 'list' | 'kanban';
|
type ViewMode = 'list' | 'kanban';
|
||||||
|
|
||||||
const PRIORITY_ORDER: Record<string, number> = { high: 0, medium: 1, low: 2 };
|
const PRIORITY_ORDER: Record<string, number> = { high: 0, medium: 1, low: 2, none: 3 };
|
||||||
|
|
||||||
function SortableTaskRow({
|
function SortableTaskRow({
|
||||||
task,
|
task,
|
||||||
|
|||||||
@ -28,7 +28,6 @@ export default function ProjectForm({ project, onClose }: ProjectFormProps) {
|
|||||||
name: project?.name || '',
|
name: project?.name || '',
|
||||||
description: project?.description || '',
|
description: project?.description || '',
|
||||||
status: project?.status || 'not_started',
|
status: project?.status || 'not_started',
|
||||||
color: project?.color || '',
|
|
||||||
due_date: project?.due_date ? project.due_date.slice(0, 10) : '',
|
due_date: project?.due_date ? project.due_date.slice(0, 10) : '',
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -84,7 +83,7 @@ export default function ProjectForm({ project, onClose }: ProjectFormProps) {
|
|||||||
<form onSubmit={handleSubmit} className="flex flex-col flex-1 overflow-hidden">
|
<form onSubmit={handleSubmit} className="flex flex-col flex-1 overflow-hidden">
|
||||||
<div className="flex-1 overflow-y-auto px-6 py-5 space-y-4">
|
<div className="flex-1 overflow-y-auto px-6 py-5 space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="name">Name</Label>
|
<Label htmlFor="name" required>Name</Label>
|
||||||
<Input
|
<Input
|
||||||
id="name"
|
id="name"
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
@ -99,7 +98,7 @@ export default function ProjectForm({ project, onClose }: ProjectFormProps) {
|
|||||||
id="description"
|
id="description"
|
||||||
value={formData.description}
|
value={formData.description}
|
||||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||||
rows={4}
|
className="min-h-[80px] flex-1"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -128,15 +127,6 @@ export default function ProjectForm({ project, onClose }: ProjectFormProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="color">Color</Label>
|
|
||||||
<Input
|
|
||||||
id="color"
|
|
||||||
type="color"
|
|
||||||
value={formData.color || '#3b82f6'}
|
|
||||||
onChange={(e) => setFormData({ ...formData, color: e.target.value })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SheetFooter>
|
<SheetFooter>
|
||||||
|
|||||||
@ -26,6 +26,7 @@ const taskStatusLabels: Record<string, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const priorityColors: Record<string, string> = {
|
const priorityColors: Record<string, string> = {
|
||||||
|
none: 'bg-gray-500/20 text-gray-400',
|
||||||
low: 'bg-green-500/20 text-green-400',
|
low: 'bg-green-500/20 text-green-400',
|
||||||
medium: 'bg-yellow-500/20 text-yellow-400',
|
medium: 'bg-yellow-500/20 text-yellow-400',
|
||||||
high: 'bg-red-500/20 text-red-400',
|
high: 'bg-red-500/20 text-red-400',
|
||||||
|
|||||||
@ -99,7 +99,7 @@ export default function TaskForm({ projectId, task, parentTaskId, onClose }: Tas
|
|||||||
<form onSubmit={handleSubmit} className="flex flex-col flex-1 overflow-hidden">
|
<form onSubmit={handleSubmit} className="flex flex-col flex-1 overflow-hidden">
|
||||||
<div className="flex-1 overflow-y-auto px-6 py-5 space-y-4">
|
<div className="flex-1 overflow-y-auto px-6 py-5 space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="title">Title</Label>
|
<Label htmlFor="title" required>Title</Label>
|
||||||
<Input
|
<Input
|
||||||
id="title"
|
id="title"
|
||||||
value={formData.title}
|
value={formData.title}
|
||||||
@ -114,7 +114,7 @@ export default function TaskForm({ projectId, task, parentTaskId, onClose }: Tas
|
|||||||
id="description"
|
id="description"
|
||||||
value={formData.description}
|
value={formData.description}
|
||||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||||
rows={3}
|
className="min-h-[80px] flex-1"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -139,6 +139,7 @@ export default function TaskForm({ projectId, task, parentTaskId, onClose }: Tas
|
|||||||
value={formData.priority}
|
value={formData.priority}
|
||||||
onChange={(e) => setFormData({ ...formData, priority: e.target.value as ProjectTask['priority'] })}
|
onChange={(e) => setFormData({ ...formData, priority: e.target.value as ProjectTask['priority'] })}
|
||||||
>
|
>
|
||||||
|
<option value="none">None</option>
|
||||||
<option value="low">Low</option>
|
<option value="low">Low</option>
|
||||||
<option value="medium">Medium</option>
|
<option value="medium">Medium</option>
|
||||||
<option value="high">High</option>
|
<option value="high">High</option>
|
||||||
|
|||||||
@ -11,6 +11,7 @@ const taskStatusColors: Record<string, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const priorityColors: Record<string, string> = {
|
const priorityColors: Record<string, string> = {
|
||||||
|
none: 'bg-gray-500/20 text-gray-400',
|
||||||
low: 'bg-green-500/20 text-green-400',
|
low: 'bg-green-500/20 text-green-400',
|
||||||
medium: 'bg-yellow-500/20 text-yellow-400',
|
medium: 'bg-yellow-500/20 text-yellow-400',
|
||||||
high: 'bg-red-500/20 text-red-400',
|
high: 'bg-red-500/20 text-red-400',
|
||||||
@ -103,35 +104,32 @@ export default function TaskRow({
|
|||||||
{task.title}
|
{task.title}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{/* Status badge */}
|
{/* Metadata columns */}
|
||||||
<Badge className={`text-[9px] px-1.5 py-0.5 shrink-0 ${taskStatusColors[task.status]}`}>
|
<Badge className={`text-[9px] px-1.5 py-0.5 shrink-0 w-16 text-center ${taskStatusColors[task.status]}`}>
|
||||||
{task.status.replace('_', ' ')}
|
{task.status.replace('_', ' ')}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|
||||||
{/* Priority pill */}
|
|
||||||
<Badge
|
<Badge
|
||||||
className={`text-[9px] px-1.5 py-0.5 rounded-full shrink-0 ${priorityColors[task.priority]}`}
|
className={`text-[9px] px-1.5 py-0.5 rounded-full shrink-0 w-14 text-center ${priorityColors[task.priority]}`}
|
||||||
>
|
>
|
||||||
{task.priority}
|
{task.priority}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|
||||||
{/* Due date */}
|
|
||||||
{task.due_date && (
|
|
||||||
<span
|
<span
|
||||||
className={`text-[11px] shrink-0 tabular-nums ${
|
className={`text-[11px] shrink-0 tabular-nums w-12 text-right ${
|
||||||
isOverdue ? 'text-red-400' : 'text-muted-foreground'
|
task.due_date
|
||||||
|
? isOverdue ? 'text-red-400' : 'text-muted-foreground'
|
||||||
|
: 'text-transparent'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{format(parseISO(task.due_date), 'MMM d')}
|
{task.due_date ? format(parseISO(task.due_date), 'MMM d') : '—'}
|
||||||
</span>
|
</span>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Subtask count */}
|
<span className={`text-[11px] shrink-0 tabular-nums w-8 text-right ${
|
||||||
{hasSubtasks && (
|
hasSubtasks ? 'text-muted-foreground' : 'text-transparent'
|
||||||
<span className="text-[11px] text-muted-foreground shrink-0 tabular-nums">
|
}`}>
|
||||||
{completedSubtasks}/{task.subtasks.length}
|
{hasSubtasks ? `${completedSubtasks}/${task.subtasks.length}` : '—'}
|
||||||
</span>
|
</span>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -68,7 +68,7 @@ export default function ReminderForm({ reminder, onClose }: ReminderFormProps) {
|
|||||||
<form onSubmit={handleSubmit} className="flex flex-col flex-1 overflow-y-auto">
|
<form onSubmit={handleSubmit} className="flex flex-col flex-1 overflow-y-auto">
|
||||||
<div className="px-6 py-5 space-y-4 flex-1">
|
<div className="px-6 py-5 space-y-4 flex-1">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="title">Title</Label>
|
<Label htmlFor="title" required>Title</Label>
|
||||||
<Input
|
<Input
|
||||||
id="title"
|
id="title"
|
||||||
value={formData.title}
|
value={formData.title}
|
||||||
@ -83,7 +83,7 @@ export default function ReminderForm({ reminder, onClose }: ReminderFormProps) {
|
|||||||
id="description"
|
id="description"
|
||||||
value={formData.description}
|
value={formData.description}
|
||||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||||
rows={4}
|
className="min-h-[80px] flex-1"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -95,7 +95,6 @@ export default function ReminderForm({ reminder, onClose }: ReminderFormProps) {
|
|||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
value={formData.remind_at}
|
value={formData.remind_at}
|
||||||
onChange={(e) => setFormData({ ...formData, remind_at: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, remind_at: e.target.value })}
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -70,7 +70,7 @@ export default function TodoForm({ todo, onClose }: TodoFormProps) {
|
|||||||
<form onSubmit={handleSubmit} className="flex flex-col flex-1 overflow-y-auto">
|
<form onSubmit={handleSubmit} className="flex flex-col flex-1 overflow-y-auto">
|
||||||
<div className="px-6 py-5 space-y-4 flex-1">
|
<div className="px-6 py-5 space-y-4 flex-1">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="title">Title</Label>
|
<Label htmlFor="title" required>Title</Label>
|
||||||
<Input
|
<Input
|
||||||
id="title"
|
id="title"
|
||||||
value={formData.title}
|
value={formData.title}
|
||||||
@ -85,7 +85,7 @@ export default function TodoForm({ todo, onClose }: TodoFormProps) {
|
|||||||
id="description"
|
id="description"
|
||||||
value={formData.description}
|
value={formData.description}
|
||||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||||
rows={4}
|
className="min-h-[80px] flex-1"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -97,6 +97,7 @@ export default function TodoForm({ todo, onClose }: TodoFormProps) {
|
|||||||
value={formData.priority}
|
value={formData.priority}
|
||||||
onChange={(e) => setFormData({ ...formData, priority: e.target.value as any })}
|
onChange={(e) => setFormData({ ...formData, priority: e.target.value as any })}
|
||||||
>
|
>
|
||||||
|
<option value="none">None</option>
|
||||||
<option value="low">Low</option>
|
<option value="low">Low</option>
|
||||||
<option value="medium">Medium</option>
|
<option value="medium">Medium</option>
|
||||||
<option value="high">High</option>
|
<option value="high">High</option>
|
||||||
|
|||||||
@ -9,7 +9,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 invalid:ring-red-500 invalid:border-red-500',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {}
|
export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {
|
||||||
|
required?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
|
const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
|
||||||
({ className, ...props }, ref) => (
|
({ className, required, children, ...props }, ref) => (
|
||||||
<label
|
<label
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
@ -12,7 +14,10 @@ const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
|
|||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
>
|
||||||
|
{children}
|
||||||
|
{required && <span className="text-red-400 ml-0.5">*</span>}
|
||||||
|
</label>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
Label.displayName = 'Label';
|
Label.displayName = 'Label';
|
||||||
|
|||||||
@ -10,7 +10,7 @@ const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
|
|||||||
<div className="relative">
|
<div className="relative">
|
||||||
<select
|
<select
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex h-10 w-full appearance-none rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
'flex h-10 w-full appearance-none rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 invalid:ring-red-500 invalid:border-red-500',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|||||||
return (
|
return (
|
||||||
<textarea
|
<textarea
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 invalid:ring-red-500 invalid:border-red-500',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|||||||
@ -25,7 +25,7 @@ export interface Todo {
|
|||||||
description?: string;
|
description?: string;
|
||||||
completed: boolean;
|
completed: boolean;
|
||||||
completed_at?: string;
|
completed_at?: string;
|
||||||
priority: 'low' | 'medium' | 'high';
|
priority: 'none' | 'low' | 'medium' | 'high';
|
||||||
due_date?: string;
|
due_date?: string;
|
||||||
category?: string;
|
category?: string;
|
||||||
recurrence_rule?: string;
|
recurrence_rule?: string;
|
||||||
@ -79,7 +79,7 @@ export interface Reminder {
|
|||||||
id: number;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
remind_at: string;
|
remind_at?: string;
|
||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
is_dismissed: boolean;
|
is_dismissed: boolean;
|
||||||
recurrence_rule?: string;
|
recurrence_rule?: string;
|
||||||
@ -113,7 +113,7 @@ export interface ProjectTask {
|
|||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
status: 'pending' | 'in_progress' | 'completed';
|
status: 'pending' | 'in_progress' | 'completed';
|
||||||
priority: 'low' | 'medium' | 'high';
|
priority: 'none' | 'low' | 'medium' | 'high';
|
||||||
due_date?: string;
|
due_date?: string;
|
||||||
person_id?: number;
|
person_id?: number;
|
||||||
sort_order: number;
|
sort_order: number;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user