Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 3 additions & 11 deletions core-web/src/components/Projects/components/CardDetailModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,14 @@ import LabelPicker from './LabelPicker';
import AssigneePicker from './AssigneePicker';
import StackedAvatars from './StackedAvatars';
import IssueComments from './IssueComments';
import { PROJECT_PRIORITY_OPTIONS } from './priorityOptions';

interface CardDetailModalProps {
card: ProjectIssue;
onClose: () => void;
initialEdit?: boolean;
}

// Priority: 4=highest, 3=high, 2=medium, 1=low,image.png 0=none
const PRIORITY_OPTIONS = [
{ value: 0, label: 'None', color: 'text-gray-300', bg: 'bg-gray-50' },
{ value: 1, label: '1', color: 'text-slate-500', bg: 'bg-slate-100' },
{ value: 2, label: '2', color: 'text-amber-500', bg: 'bg-amber-50' },
{ value: 3, label: '3', color: 'text-orange-500', bg: 'bg-orange-50' },
{ value: 4, label: '4', color: 'text-rose-500', bg: 'bg-rose-50' },
] as const;

export default function CardDetailModal({ card, onClose, initialEdit = false }: CardDetailModalProps) {
const [isEditing, setIsEditing] = useState(initialEdit);
const [isVisible, setIsVisible] = useState(true);
Expand Down Expand Up @@ -95,7 +87,7 @@ export default function CardDetailModal({ card, onClose, initialEdit = false }:
const imageInputRef = useRef<HTMLInputElement>(null);

const column = states.find((s) => s.id === card.state_id);
const priorityConfig = PRIORITY_OPTIONS.find((p) => p.value === priority);
const priorityConfig = PROJECT_PRIORITY_OPTIONS.find((p) => p.value === priority);

// Track which images were added/removed for atomic operations
const [addedImageKeys, setAddedImageKeys] = useState<string[]>([]);
Expand Down Expand Up @@ -353,7 +345,7 @@ export default function CardDetailModal({ card, onClose, initialEdit = false }:
<span className="text-gray-400">No priority</span>
{priority === 0 && <span className="ml-auto text-blue-600">✓</span>}
</button>
{PRIORITY_OPTIONS.filter((o) => o.value > 0).map((option) => (
{PROJECT_PRIORITY_OPTIONS.filter((o) => o.value > 0).map((option) => (
<button
key={option.value}
onClick={() => {
Expand Down
26 changes: 10 additions & 16 deletions core-web/src/components/Projects/components/KanbanCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { lastDragEndTime } from './KanbanBoard';
import ConfirmModal from './ConfirmModal';
import AssigneePicker from './AssigneePicker';
import DatePicker from '../../ui/DatePicker';
import { PROJECT_PRIORITY_OPTIONS } from './priorityOptions';

interface KanbanCardProps {
card: ProjectIssue;
Expand All @@ -18,14 +19,6 @@ interface KanbanCardProps {
isDragActive?: boolean;
}

// Priority: 4=highest, 3=high, 2=medium, 1=low, 0=none
const PRIORITY_CONFIG: Record<number, { color: string; bg: string }> = {
4: { color: 'text-rose-500', bg: 'bg-rose-50' },
3: { color: 'text-orange-500', bg: 'bg-orange-50' },
2: { color: 'text-amber-500', bg: 'bg-amber-50' },
1: { color: 'text-slate-500', bg: 'bg-slate-100' },
};

// Memoized label tag to avoid inline style object recreation
const LabelTag = memo(function LabelTag({ color, name }: { color: string; name: string }) {
const style = useMemo(() => ({
Expand Down Expand Up @@ -121,7 +114,8 @@ const KanbanCard = memo(function KanbanCard({
}
};

const priorityConfig = card.priority ? PRIORITY_CONFIG[card.priority] : null;
const priorityConfig =
PROJECT_PRIORITY_OPTIONS.find((option) => option.value === card.priority) ?? null;

const cardClasses = [
'group relative bg-white px-4 py-3 rounded-lg mb-2 cursor-default active:cursor-grabbing',
Expand Down Expand Up @@ -194,19 +188,19 @@ const KanbanCard = memo(function KanbanCard({
>
<span>–</span>
</button>
{[1, 2, 3, 4].map((p) => (
{PROJECT_PRIORITY_OPTIONS.filter((option) => option.value > 0).map((option) => (
<button
key={p}
key={option.value}
onClick={(e) => {
e.stopPropagation();
handlePriorityChange(p);
handlePriorityChange(option.value);
}}
className={`flex items-center gap-1 px-1.5 py-1 rounded-md transition-all text-[11px] font-medium ${PRIORITY_CONFIG[p].color} ${
card.priority === p ? PRIORITY_CONFIG[p].bg : 'opacity-50 hover:opacity-100'
className={`flex items-center gap-1 px-1.5 py-1 rounded-md transition-all text-[11px] font-medium ${option.color} ${
card.priority === option.value ? option.bg : 'opacity-50 hover:opacity-100'
}`}
title={`Priority ${p}`}
title={option.label}
>
<span>P{p}</span>
<span>P{option.value}</span>
<Flag size={12} weight="fill" />
</button>
))}
Expand Down
14 changes: 4 additions & 10 deletions core-web/src/components/Projects/components/PriorityPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createPortal } from 'react-dom';
import { Flag } from '@phosphor-icons/react';
import { CheckIcon } from '@heroicons/react/24/outline';
import { useUpdateIssue } from '../../../hooks/queries/useProjects';
import { PROJECT_PRIORITY_OPTIONS } from './priorityOptions';

interface PriorityPickerProps {
issueId: string;
Expand All @@ -11,14 +12,6 @@ interface PriorityPickerProps {
buttonClassName?: string;
}

const PRIORITY_OPTIONS = [
{ value: 0, label: 'No priority', color: 'text-gray-400', bg: 'bg-gray-50' },
{ value: 1, label: 'Urgent', color: 'text-rose-500', bg: 'bg-rose-50' },
{ value: 2, label: 'High', color: 'text-orange-500', bg: 'bg-orange-50' },
{ value: 3, label: 'Medium', color: 'text-amber-500', bg: 'bg-amber-50' },
{ value: 4, label: 'Low', color: 'text-slate-500', bg: 'bg-slate-100' },
] as const;

export default function PriorityPicker({ issueId, boardId, priority, buttonClassName = '' }: PriorityPickerProps) {
const [isOpen, setIsOpen] = useState(false);
const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0 });
Expand All @@ -28,7 +21,8 @@ export default function PriorityPicker({ issueId, boardId, priority, buttonClass
// React Query mutations
const updateIssue = useUpdateIssue(boardId ?? null);

const currentOption = PRIORITY_OPTIONS.find((p) => p.value === priority) || PRIORITY_OPTIONS[0];
const currentOption =
PROJECT_PRIORITY_OPTIONS.find((p) => p.value === priority) || PROJECT_PRIORITY_OPTIONS[0];

const handleUpdate = (value: number) => {
setIsOpen(false);
Expand Down Expand Up @@ -94,7 +88,7 @@ export default function PriorityPicker({ issueId, boardId, priority, buttonClass
className="fixed z-[100] bg-white rounded-lg shadow-lg border border-gray-200 overflow-hidden py-1 w-40"
style={{ top: dropdownPosition.top, left: dropdownPosition.left }}
>
{PRIORITY_OPTIONS.map((option) => (
{PROJECT_PRIORITY_OPTIONS.map((option) => (
<button
key={option.value}
onClick={(e) => {
Expand Down
60 changes: 9 additions & 51 deletions core-web/src/components/Projects/components/ProjectsFilterBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,50 +6,7 @@ import { Icon } from "../../ui/Icon";
import { Flag } from "@phosphor-icons/react";
import { useProjectsStore } from "../../../stores/projectsStore";
import { useProjectBoard, useProjectMembers } from "../../../hooks/queries/useProjects";

// Priority options matching CardDetailModal
const PRIORITY_CONFIG = [
{
value: 0,
label: "None",
shortLabel: "None",
color: "text-gray-400",
bg: "bg-gray-50",
activeBg: "bg-gray-100 ring-1 ring-gray-200",
},
{
value: 1,
label: "Priority 1",
shortLabel: "P1",
color: "text-slate-500",
bg: "bg-slate-50",
activeBg: "bg-slate-100 ring-1 ring-slate-200",
},
{
value: 2,
label: "Priority 2",
shortLabel: "P2",
color: "text-amber-500",
bg: "bg-amber-50",
activeBg: "bg-amber-100 ring-1 ring-amber-200",
},
{
value: 3,
label: "Priority 3",
shortLabel: "P3",
color: "text-orange-500",
bg: "bg-orange-50",
activeBg: "bg-orange-100 ring-1 ring-orange-200",
},
{
value: 4,
label: "Priority 4",
shortLabel: "P4",
color: "text-rose-500",
bg: "bg-rose-50",
activeBg: "bg-rose-100 ring-1 ring-rose-200",
},
] as const;
import { PROJECT_PRIORITY_OPTIONS } from "./priorityOptions";

interface ProjectsFilterBarProps {
viewMode: "kanban" | "list";
Expand All @@ -73,13 +30,13 @@ export default function ProjectsFilterBar({
// React Query data
const { data: boardData } = useProjectBoard(activeProjectId);
const { data: members = [] } = useProjectMembers(workspaceId);
const boardStates = boardData?.states;
const boardLabels = boardData?.labels;

const columns = useMemo(() => {
if (!boardData?.states) return [];
return [...boardData.states].sort((a, b) => a.position - b.position);
}, [boardData?.states]);

const boardLabels = boardData?.labels ?? [];
if (!boardStates) return [];
return [...boardStates].sort((a, b) => a.position - b.position);
}, [boardStates]);

useEffect(() => {
if (!isOpen) return;
Expand Down Expand Up @@ -119,6 +76,7 @@ export default function ProjectsFilterBar({
}, [members]);

const labelOptions = useMemo(() => {
if (!boardLabels) return [];
return boardLabels.map((label) => ({
id: label.id,
label: label.name,
Expand Down Expand Up @@ -232,7 +190,7 @@ export default function ProjectsFilterBar({
Priority
</div>
<div className="flex flex-wrap gap-2 -m-0.5 p-0.5">
{PRIORITY_CONFIG.map((option) => {
{PROJECT_PRIORITY_OPTIONS.map((option) => {
const isSelected = filters.priorities.includes(
option.value,
);
Expand Down Expand Up @@ -373,7 +331,7 @@ export default function ProjectsFilterBar({
<AnimatePresence mode="popLayout">
{/* Active Priority Filters */}
{filters.priorities.map((priority) => {
const config = PRIORITY_CONFIG.find((p) => p.value === priority);
const config = PROJECT_PRIORITY_OPTIONS.find((p) => p.value === priority);
if (!config) return null;
return (
<motion.div
Expand Down
53 changes: 53 additions & 0 deletions core-web/src/components/Projects/components/priorityOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
export type ProjectPriorityValue = 0 | 1 | 2 | 3 | 4;

export interface ProjectPriorityOption {
value: ProjectPriorityValue;
label: string;
shortLabel: string;
color: string;
bg: string;
activeBg: string;
}

export const PROJECT_PRIORITY_OPTIONS = [
{
value: 0,
label: 'No priority',
shortLabel: 'None',
color: 'text-gray-400',
bg: 'bg-gray-50',
activeBg: 'bg-gray-100 ring-1 ring-gray-200',
},
{
value: 1,
label: 'Urgent',
shortLabel: 'P1',
color: 'text-rose-500',
bg: 'bg-rose-50',
activeBg: 'bg-rose-100 ring-1 ring-rose-200',
},
{
value: 2,
label: 'High',
shortLabel: 'P2',
color: 'text-orange-500',
bg: 'bg-orange-50',
activeBg: 'bg-orange-100 ring-1 ring-orange-200',
},
{
value: 3,
label: 'Medium',
shortLabel: 'P3',
color: 'text-amber-500',
bg: 'bg-amber-50',
activeBg: 'bg-amber-100 ring-1 ring-amber-200',
},
{
value: 4,
label: 'Low',
shortLabel: 'P4',
color: 'text-slate-500',
bg: 'bg-slate-100',
activeBg: 'bg-slate-100 ring-1 ring-slate-200',
},
] as const satisfies readonly ProjectPriorityOption[];
Loading