Skip to content

Commit fdbfeac

Browse files
committed
Refine card detail status interactions
1 parent a6b9feb commit fdbfeac

17 files changed

Lines changed: 71 additions & 68 deletions

File tree

app/_components/FeatureComponents/Kanban/Kanban.tsx

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
CollisionDetection,
1515
rectIntersection,
1616
} from "@dnd-kit/core";
17-
import { Checklist, KanbanStatus } from "@/app/_types";
17+
import { Checklist, Item, KanbanStatus } from "@/app/_types";
1818
import { KanbanColumn } from "./KanbanColumn";
1919
import { KanbanCard } from "./KanbanCard";
2020
import { ChecklistHeading } from "../Checklists/Parts/Common/ChecklistHeading";
@@ -48,6 +48,17 @@ interface KanbanBoardProps {
4848
onUpdate: (updatedChecklist: Checklist) => void;
4949
}
5050

51+
const _findItemById = (items: Item[], itemId: string): Item | null => {
52+
for (const item of items) {
53+
if (item.id === itemId) return item;
54+
if (item.children) {
55+
const found = _findItemById(item.children, itemId);
56+
if (found) return found;
57+
}
58+
}
59+
return null;
60+
};
61+
5162
export const Kanban = ({ checklist, onUpdate }: KanbanBoardProps) => {
5263
const t = useTranslations();
5364
const [isClient, setIsClient] = useState(false);
@@ -57,6 +68,7 @@ export const Kanban = ({ checklist, onUpdate }: KanbanBoardProps) => {
5768
const [searchQuery, setSearchQuery] = useState("");
5869
const [priorityFilter, setPriorityFilter] = useState("");
5970
const [assigneeFilter, setAssigneeFilter] = useState("");
71+
const [detailItemId, setDetailItemId] = useState<string | null>(null);
6072
const [calendarSelectedItem, setCalendarSelectedItem] = useState<
6173
import("@/app/_types").Item | null
6274
>(null);
@@ -134,6 +146,10 @@ export const Kanban = ({ checklist, onUpdate }: KanbanBoardProps) => {
134146
);
135147

136148
const archivedItems = localChecklist.items.filter((item) => item.isArchived);
149+
const detailItem = useMemo(
150+
() => (detailItemId ? _findItemById(localChecklist.items, detailItemId) : null),
151+
[detailItemId, localChecklist.items],
152+
);
137153

138154
const handleUnarchive = async (itemId: string) => {
139155
const formData = new FormData();
@@ -216,6 +232,7 @@ export const Kanban = ({ checklist, onUpdate }: KanbanBoardProps) => {
216232
checklistId={localChecklist.id}
217233
category={localChecklist.category || "Uncategorized"}
218234
onUpdate={handleItemUpdate}
235+
onOpenDetail={(item) => setDetailItemId(item.id)}
219236
isShared={isShared}
220237
statusColor={statuses.find((s) => s.id === column.id)?.color}
221238
statuses={statuses}
@@ -415,6 +432,7 @@ export const Kanban = ({ checklist, onUpdate }: KanbanBoardProps) => {
415432
checklistId={localChecklist.id}
416433
category={localChecklist.category || "Uncategorized"}
417434
onUpdate={refreshChecklist}
435+
onOpenDetail={() => {}}
418436
isShared={isShared}
419437
statuses={statuses}
420438
/>
@@ -445,6 +463,18 @@ export const Kanban = ({ checklist, onUpdate }: KanbanBoardProps) => {
445463
/>
446464
)}
447465

466+
{detailItem && (
467+
<KanbanCardDetail
468+
checklist={localChecklist}
469+
item={detailItem}
470+
isOpen={!!detailItem}
471+
onClose={() => setDetailItemId(null)}
472+
onUpdate={handleItemUpdate}
473+
checklistId={localChecklist.id}
474+
category={localChecklist.category || "Uncategorized"}
475+
/>
476+
)}
477+
448478
{showBulkPasteModal && (
449479
<BulkPasteModal
450480
isOpen={showBulkPasteModal}

app/_components/FeatureComponents/Kanban/KanbanCard.tsx

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { Dropdown } from "@/app/_components/GlobalComponents/Dropdowns/Dropdown"
88
import { Modal } from "@/app/_components/GlobalComponents/Modals/Modal";
99
import { useState, useEffect, memo, useMemo, useCallback } from "react";
1010
import { TaskStatus } from "@/app/_types/enums";
11-
import { KanbanCardDetail } from "./KanbanCardDetail";
1211
import { useAppMode } from "@/app/_providers/AppModeProvider";
1312
import { useKanbanItem } from "@/app/_hooks/kanban/useKanbanItem";
1413
import {
@@ -37,6 +36,7 @@ interface KanbanCardProps {
3736
checklistId: string;
3837
category: string;
3938
onUpdate: (updatedChecklist: Checklist) => void;
39+
onOpenDetail: (item: Item) => void;
4040
isShared: boolean;
4141
statuses: KanbanStatus[];
4242
statusColor?: string;
@@ -49,6 +49,7 @@ const KanbanCardComponent = ({
4949
checklistId,
5050
category,
5151
onUpdate,
52+
onOpenDetail,
5253
isShared,
5354
statuses,
5455
statusColor,
@@ -71,7 +72,6 @@ const KanbanCardComponent = ({
7172
[usersPublicData],
7273
);
7374

74-
const [showDetailModal, setShowDetailModal] = useState(false);
7575
const [showTimeEntriesModal, setShowTimeEntriesModal] = useState(false);
7676
const [showStatusSheet, setShowStatusSheet] = useState(false);
7777
const hideMobileStatusDropdown = user?.hideMobileStatusDropdown === "enable";
@@ -123,6 +123,20 @@ const KanbanCardComponent = ({
123123
setShowStatusSheet(false);
124124
};
125125

126+
const handleOpenStatusSheet = () => {
127+
if (typeof window !== "undefined" && window.innerWidth >= 1024) return;
128+
setShowStatusSheet(true);
129+
};
130+
131+
useEffect(() => {
132+
if (!showStatusSheet) return;
133+
const handleResize = () => {
134+
if (window.innerWidth >= 1024) setShowStatusSheet(false);
135+
};
136+
window.addEventListener("resize", handleResize);
137+
return () => window.removeEventListener("resize", handleResize);
138+
}, [showStatusSheet]);
139+
126140
return (
127141
<>
128142
{showStatusSheet && (
@@ -172,26 +186,14 @@ const KanbanCardComponent = ({
172186
/>
173187
)}
174188

175-
{showDetailModal && (
176-
<KanbanCardDetail
177-
checklist={checklist}
178-
item={item}
179-
isOpen={showDetailModal}
180-
onClose={() => setShowDetailModal(false)}
181-
onUpdate={onUpdate}
182-
checklistId={checklistId}
183-
category={category}
184-
/>
185-
)}
186-
187189
<div className="min-w-0">
188190
<div
189191
ref={setNodeRef}
190192
style={style}
191193
{...attributes}
192194
{...listeners}
193195
aria-label={item.text}
194-
onDoubleClick={() => setShowDetailModal(true)}
196+
onDoubleClick={() => onOpenDetail(item)}
195197
className={cn(
196198
"group bg-background border rounded-jotty p-3 transition-all duration-200 hover:shadow-md cursor-grab active:cursor-grabbing min-w-0",
197199
getStatusColor(item.status),
@@ -212,8 +214,8 @@ const KanbanCardComponent = ({
212214
onEditTextChange={kanbanItemHook.setEditText}
213215
onEditSave={kanbanItemHook.handleSave}
214216
onEditKeyDown={kanbanItemHook.handleKeyDown}
215-
onShowSubtaskModal={() => setShowDetailModal(true)}
216-
onShowStatusMenu={() => setShowStatusSheet(true)}
217+
onShowSubtaskModal={() => onOpenDetail(item)}
218+
onShowStatusMenu={handleOpenStatusSheet}
217219
onEdit={kanbanItemHook.handleEdit}
218220
onDelete={kanbanItemHook.handleDelete}
219221
onArchive={kanbanItemHook.handleArchive}

app/_components/FeatureComponents/Kanban/KanbanCardDetailProperties.tsx

Lines changed: 18 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -162,15 +162,13 @@ export const KanbanCardDetailProperties = ({
162162
return (
163163
<div className="space-y-4">
164164
{canEdit && (
165-
<PropertySection title={t("kanban.properties")}>
166-
<div>
167-
<label className="block text-xs font-medium text-muted-foreground mb-2">
168-
{t("kanban.status")}
169-
</label>
165+
<>
166+
<PropertySection title={t("kanban.status")}>
170167
<div className="flex flex-wrap gap-1.5">
171168
{sortedStatuses.map((status) => (
172169
<button
173170
key={status.id}
171+
type="button"
174172
onClick={() => onStatusChange(status.id)}
175173
className={cn(
176174
"text-[11px] px-2.5 py-1.5 rounded-jotty border transition-all flex items-center gap-1.5",
@@ -187,16 +185,14 @@ export const KanbanCardDetailProperties = ({
187185
</button>
188186
))}
189187
</div>
190-
</div>
188+
</PropertySection>
191189

192-
<div>
193-
<label className="block text-xs font-medium text-muted-foreground mb-2">
194-
{t("kanban.priority")}
195-
</label>
190+
<PropertySection title={t("kanban.priority")}>
196191
<div className="flex flex-wrap gap-1.5">
197192
{priorities.map((p) => (
198193
<button
199194
key={p}
195+
type="button"
200196
onClick={() => onPriorityChange(p)}
201197
className={cn(
202198
"text-[11px] px-2.5 py-1.5 rounded-jotty border transition-all flex items-center gap-1.5",
@@ -215,12 +211,9 @@ export const KanbanCardDetailProperties = ({
215211
</button>
216212
))}
217213
</div>
218-
</div>
214+
</PropertySection>
219215

220-
<div>
221-
<label className="block text-xs font-medium text-muted-foreground mb-2">
222-
{t("kanban.score")}
223-
</label>
216+
<PropertySection title={t("kanban.score")}>
224217
<input
225218
type="number"
226219
min="0"
@@ -232,47 +225,35 @@ export const KanbanCardDetailProperties = ({
232225
className="w-full px-3 py-2 text-sm bg-background border border-input rounded-jotty focus:outline-none focus:border-ring transition-colors"
233226
placeholder="0"
234227
/>
235-
</div>
228+
</PropertySection>
236229

237230
{isShared && (
238-
<div>
239-
<label className="block text-xs font-medium text-muted-foreground mb-2">
240-
{t("kanban.assignee")}
241-
</label>
231+
<PropertySection title={t("kanban.assignee")}>
242232
<Dropdown
243233
value={assigneeInput}
244234
options={assigneeOptions}
245235
onChange={onAssigneeChange}
246236
placeholder={t("kanban.unassigned")}
247237
/>
248-
</div>
238+
</PropertySection>
249239
)}
250240

251-
<div>
252-
<label className="block text-xs font-medium text-muted-foreground mb-2">
253-
{t("kanban.reminder")}
254-
</label>
241+
<PropertySection title={t("kanban.reminder")}>
255242
<DateTimePicker
256243
value={reminderInput}
257244
onChange={onReminderChange}
258245
onBlur={onReminderSave}
259246
/>
260-
</div>
247+
</PropertySection>
261248

262-
<div>
263-
<label className="block text-xs font-medium text-muted-foreground mb-2">
264-
{t("kanban.targetDate")}
265-
</label>
249+
<PropertySection title={t("kanban.targetDate")}>
266250
<DatePicker
267251
value={targetDateInput}
268252
onChange={onTargetDateChange}
269253
/>
270-
</div>
254+
</PropertySection>
271255

272-
<div>
273-
<label className="block text-xs font-medium text-muted-foreground mb-2">
274-
{t("kanban.estimatedTime")}
275-
</label>
256+
<PropertySection title={t("kanban.estimatedTime")}>
276257
<input
277258
type="number"
278259
min="0"
@@ -292,8 +273,8 @@ export const KanbanCardDetailProperties = ({
292273
})}
293274
</p>
294275
)}
295-
</div>
296-
</PropertySection>
276+
</PropertySection>
277+
</>
297278
)}
298279

299280
{metadata.length > 0 && (

app/_components/FeatureComponents/Kanban/KanbanColumn.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ interface KanbanColumnProps {
2323
checklistId: string;
2424
category: string;
2525
onUpdate: (updatedChecklist: Checklist) => void;
26+
onOpenDetail: (item: Item) => void;
2627
isShared: boolean;
2728
statusColor?: string;
2829
statuses: KanbanStatus[];
@@ -85,6 +86,7 @@ const KanbanColumnComponent = ({
8586
category,
8687
isShared,
8788
onUpdate,
89+
onOpenDetail,
8890
statusColor,
8991
statuses,
9092
onAddItem,
@@ -198,6 +200,7 @@ const KanbanColumnComponent = ({
198200
checklistId={checklistId}
199201
category={category}
200202
onUpdate={onUpdate}
203+
onOpenDetail={onOpenDetail}
201204
isShared={isShared}
202205
statuses={statuses}
203206
statusColor={statusColor}

app/_translations/de.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,6 @@
543543
"autoComplete": "Automatisch abschließen"
544544
},
545545
"kanban": {
546-
"properties": "Eigenschaften",
547546
"status": "Status",
548547
"priority": "Priorität",
549548
"score": "Punktzahl",

app/_translations/en.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,6 @@
568568
"autoComplete": "Auto complete"
569569
},
570570
"kanban": {
571-
"properties": "Properties",
572571
"status": "Status",
573572
"priority": "Priority",
574573
"score": "Score",

app/_translations/es.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,6 @@
543543
"autoComplete": "Autocompletar"
544544
},
545545
"kanban": {
546-
"properties": "Propiedades",
547546
"status": "Estado",
548547
"priority": "Prioridad",
549548
"score": "Puntuación",

app/_translations/fr.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,6 @@
543543
"autoComplete": "Complétion automatique"
544544
},
545545
"kanban": {
546-
"properties": "Propriétés",
547546
"status": "Statut",
548547
"priority": "Priorité",
549548
"score": "Score",

app/_translations/it.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,6 @@
543543
"autoComplete": "Complete automaticamente"
544544
},
545545
"kanban": {
546-
"properties": "Proprietà",
547546
"status": "Stato",
548547
"priority": "Priorità",
549548
"score": "Punteggio",

app/_translations/klingon.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,6 @@
568568
"autoComplete": "nIteb rIn"
569569
},
570570
"kanban": {
571-
"properties": "DI'onmey",
572571
"status": "ghu'",
573572
"priority": "nom mIw",
574573
"score": "pong",

0 commit comments

Comments
 (0)