Skip to content

Commit fecc3ec

Browse files
authored
Merge pull request #523 from fccview/develop
Lift off!
2 parents 3a7c22f + 3307091 commit fecc3ec

102 files changed

Lines changed: 8584 additions & 2200 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/(loggedInRoutes)/settings/connections/page.tsx

Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { LinksTab } from "@/app/_components/FeatureComponents/Profile/Parts/LinksTab";
22
import { readLinkIndex } from "@/app/_server/actions/link";
3-
import { LinkIndex } from "@/app/_types";
43
import { getUsername } from "@/app/_server/actions/users";
54
import { getArchivedItems } from "@/app/_server/actions/archived";
65
import { getUserNotes } from "@/app/_server/actions/note";
76
import { getUserChecklists } from "@/app/_server/actions/checklist";
7+
import { filterArchivedLinkIndex } from "@/app/_components/FeatureComponents/Profile/Parts/ConnectionsGraph/graph-data";
88

99
export default async function ConnectionsPage() {
1010
const username = await getUsername();
@@ -16,52 +16,7 @@ export default async function ConnectionsPage() {
1616
]);
1717

1818
const archivedItems = archivedResult.success ? archivedResult.data : [];
19-
20-
const filterArchivedItems = (linkIndex: LinkIndex, archivedItems: any[]): LinkIndex => {
21-
const archivedIds = new Set(archivedItems.map(item => `${item.category || 'Uncategorized'}/${item.id}`));
22-
23-
const filteredNotes = Object.fromEntries(
24-
Object.entries(linkIndex.notes).filter(([key]) => !archivedIds.has(key))
25-
);
26-
27-
const filteredChecklists = Object.fromEntries(
28-
Object.entries(linkIndex.checklists).filter(([key]) => !archivedIds.has(key))
29-
);
30-
31-
Object.keys(filteredNotes).forEach(noteKey => {
32-
filteredNotes[noteKey].isLinkedTo.notes = filteredNotes[noteKey].isLinkedTo.notes.filter(
33-
linkedKey => !archivedIds.has(linkedKey)
34-
);
35-
filteredNotes[noteKey].isLinkedTo.checklists = filteredNotes[noteKey].isLinkedTo.checklists.filter(
36-
linkedKey => !archivedIds.has(linkedKey)
37-
);
38-
filteredNotes[noteKey].isReferencedIn.notes = filteredNotes[noteKey].isReferencedIn.notes.filter(
39-
refKey => !archivedIds.has(refKey)
40-
);
41-
filteredNotes[noteKey].isReferencedIn.checklists = filteredNotes[noteKey].isReferencedIn.checklists.filter(
42-
refKey => !archivedIds.has(refKey)
43-
);
44-
});
45-
46-
Object.keys(filteredChecklists).forEach(checklistKey => {
47-
filteredChecklists[checklistKey].isLinkedTo.notes = filteredChecklists[checklistKey].isLinkedTo.notes.filter(
48-
linkedKey => !archivedIds.has(linkedKey)
49-
);
50-
filteredChecklists[checklistKey].isLinkedTo.checklists = filteredChecklists[checklistKey].isLinkedTo.checklists.filter(
51-
linkedKey => !archivedIds.has(linkedKey)
52-
);
53-
filteredChecklists[checklistKey].isReferencedIn.notes = filteredChecklists[checklistKey].isReferencedIn.notes.filter(
54-
refKey => !archivedIds.has(refKey)
55-
);
56-
filteredChecklists[checklistKey].isReferencedIn.checklists = filteredChecklists[checklistKey].isReferencedIn.checklists.filter(
57-
refKey => !archivedIds.has(refKey)
58-
);
59-
});
60-
61-
return { notes: filteredNotes, checklists: filteredChecklists };
62-
};
63-
64-
const filteredLinkIndex = filterArchivedItems(linkIndex, archivedItems || []);
19+
const filteredLinkIndex = filterArchivedLinkIndex(linkIndex, archivedItems || []);
6520
const notes = notesResult.success ? notesResult.data || [] : [];
6621
const checklists = checklistsResult.success ? checklistsResult.data || [] : [];
6722

app/_components/FeatureComponents/Home/Parts/ChecklistHome.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ export const ChecklistHome = ({
164164
}
165165

166166
return (
167-
<div className="h-full overflow-y-auto hide-scrollbar bg-background pb-16 lg:pb-0 jotty-scrollable-content">
167+
<div className="h-full overflow-y-auto bg-background pb-16 lg:pb-0 jotty-scrollable-content">
168168
<div className="max-w-full pt-6 pb-4 px-4 lg:pt-8 lg:pb-8 lg:px-8">
169169
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6 lg:mb-8">
170170
<div className="flex items-center gap-3">

app/_components/FeatureComponents/Home/Parts/NotesHome.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ export const NotesHome = ({
159159
}
160160

161161
return (
162-
<div className="flex-1 overflow-y-auto jotty-scrollable-content bg-background h-full hide-scrollbar">
162+
<div className="flex-1 overflow-y-auto jotty-scrollable-content bg-background h-full">
163163
<div className="max-w-full pt-6 pb-4 px-4 lg:pt-8 lg:pb-8 lg:px-8">
164164
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6 lg:mb-8">
165165
<div className="flex items-center gap-3">

app/_components/FeatureComponents/Kanban/ArchivedItemsModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export const ArchivedItemsModal = ({
111111
<p className="text-md lg:text-xs text-muted-foreground">
112112
{t('common.archived')}{" "}
113113
{formatDateString(item.archivedAt)}
114-
{item.archivedBy && ` ${t('common.by')} ${item.archivedBy}`}
114+
{item.archivedBy && ` ${t('common.by', { owner: item.archivedBy })}`}
115115
</p>
116116
)}
117117
</div>

app/_components/FeatureComponents/Kanban/CalendarView.tsx

Lines changed: 99 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use client";
22

3+
import { KeyboardEvent } from "react";
34
import { Checklist, Item } from "@/app/_types";
45
import { useCalendarView } from "@/app/_hooks/kanban/useCalendarView";
56
import { Button } from "@/app/_components/GlobalComponents/Buttons/Button";
@@ -10,7 +11,11 @@ import {
1011
Download04Icon,
1112
} from "hugeicons-react";
1213
import { cn } from "@/app/_utils/global-utils";
13-
import { getPriorityDotColor } from "@/app/_utils/kanban/index";
14+
import { getPriorityBarStyle } from "@/app/_utils/kanban/index";
15+
import {
16+
getMaxBarLanes,
17+
getWeekBarSegments,
18+
} from "@/app/_utils/kanban/calendar-utils";
1419
import { useTranslations } from "next-intl";
1520

1621
interface CalendarViewProps {
@@ -33,16 +38,18 @@ const _toLocalDate = (d: Date): string => {
3338
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
3439
};
3540

41+
const _BAR_HEIGHT = 18;
42+
3643
export const CalendarView = ({ checklist, onItemClick }: CalendarViewProps) => {
3744
const t = useTranslations();
3845
const {
3946
currentDate,
4047
calendarGrid,
48+
events,
4149
goToPreviousMonth,
4250
goToNextMonth,
4351
goToToday,
4452
exportICS,
45-
getEventsForDate,
4653
unscheduledItems,
4754
} = useCalendarView(checklist);
4855

@@ -53,6 +60,11 @@ export const CalendarView = ({ checklist, onItemClick }: CalendarViewProps) => {
5360
year: "numeric",
5461
});
5562

63+
const _openItem = (itemId: string) => {
64+
const item = checklist.items.find((entry) => entry.id === itemId);
65+
if (item && onItemClick) onItemClick(item);
66+
};
67+
5668
return (
5769
<div className="space-y-4 min-w-0">
5870
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between min-w-0">
@@ -88,78 +100,99 @@ export const CalendarView = ({ checklist, onItemClick }: CalendarViewProps) => {
88100
))}
89101
</div>
90102

91-
{calendarGrid.map((week, weekIndex) => (
92-
<div key={weekIndex} className="grid grid-cols-7">
93-
{week.map((day, dayIndex) => {
94-
if (!day) {
95-
return (
96-
<div
97-
key={`empty-${dayIndex}`}
98-
className="min-h-[100px] p-1 border-b border-r border-border bg-muted/20"
99-
/>
100-
);
101-
}
103+
{calendarGrid.map((week, weekIndex) => {
104+
const segments = getWeekBarSegments(week, events);
105+
const maxLanes = getMaxBarLanes(segments);
106+
const barAreaHeight = maxLanes * _BAR_HEIGHT;
102107

103-
const dateStr = _toLocalDate(day);
104-
const dayEvents = getEventsForDate(day);
105-
const isToday = dateStr === today;
108+
return (
109+
<div key={weekIndex} className="relative grid grid-cols-7">
110+
{week.map((day, dayIndex) => {
111+
if (!day) {
112+
return (
113+
<div
114+
key={`empty-${dayIndex}`}
115+
className="border-b border-r border-border bg-muted/20"
116+
style={{ minHeight: 72 + barAreaHeight }}
117+
/>
118+
);
119+
}
106120

107-
return (
108-
<div
109-
key={dateStr}
110-
className={cn(
111-
"min-h-[100px] p-1 border-b border-r border-border transition-colors",
112-
isToday && "bg-primary/5",
113-
)}
114-
>
121+
const dateStr = _toLocalDate(day);
122+
const isToday = dateStr === today;
123+
124+
return (
115125
<div
126+
key={dateStr}
116127
className={cn(
117-
"text-xs font-medium mb-1 w-6 h-6 flex items-center justify-center rounded-full",
118-
isToday && "bg-primary text-primary-foreground",
128+
"border-b border-r border-border p-1 transition-colors",
129+
isToday && "bg-primary/5",
119130
)}
131+
style={{ minHeight: 72 + barAreaHeight }}
120132
>
121-
{day.getDate()}
122-
</div>
123-
<div className="space-y-0.5">
124-
{dayEvents.slice(0, 3).map((event) => (
125-
<div
126-
key={event.id}
127-
onClick={() => {
128-
const item = checklist.items.find(
129-
(i) => i.id === event.itemId,
130-
);
131-
if (item && onItemClick) onItemClick(item);
132-
}}
133-
className={cn(
134-
"text-[10px] px-1 py-0.5 rounded truncate cursor-pointer hover:opacity-80 transition-opacity flex items-center gap-1 bg-muted text-muted-foreground border-l-2",
135-
event.completed && "line-through opacity-60",
136-
)}
137-
style={{
138-
borderLeftColor: event.completed
139-
? "#22c55e"
140-
: getPriorityDotColor(
141-
event.priority as Parameters<
142-
typeof getPriorityDotColor
143-
>[0],
144-
),
145-
}}
146-
>
147-
{event.title}
148-
</div>
149-
))}
150-
{dayEvents.length > 3 && (
151-
<div className="text-[10px] text-muted-foreground px-1">
152-
{t("kanban.moreEvents", {
153-
count: dayEvents.length - 3,
154-
})}
155-
</div>
156-
)}
133+
<div
134+
className={cn(
135+
"text-xs font-medium mb-1 w-6 h-6 flex items-center justify-center rounded-full",
136+
isToday && "bg-primary text-primary-foreground",
137+
)}
138+
>
139+
{day.getDate()}
140+
</div>
157141
</div>
142+
);
143+
})}
144+
145+
{maxLanes > 0 && (
146+
<div
147+
className="absolute inset-x-0 top-7 grid grid-cols-7 gap-y-0.5 px-0.5 pointer-events-none"
148+
style={{ gridTemplateRows: `repeat(${maxLanes}, ${_BAR_HEIGHT}px)` }}
149+
>
150+
{segments.map((segment) => {
151+
const barStyle = getPriorityBarStyle(
152+
segment.event.priority as Parameters<typeof getPriorityBarStyle>[0],
153+
);
154+
155+
return (
156+
<div
157+
key={`${segment.event.id}-${segment.colStart}-${segment.lane}`}
158+
{...(onItemClick
159+
? {
160+
role: "button" as const,
161+
tabIndex: 0,
162+
"aria-label": segment.event.title,
163+
onClick: () => _openItem(segment.event.itemId),
164+
onKeyDown: (e: KeyboardEvent) => {
165+
if (e.key === "Enter" || e.key === " ") {
166+
e.preventDefault();
167+
_openItem(segment.event.itemId);
168+
}
169+
},
170+
}
171+
: {})}
172+
className={cn(
173+
"pointer-events-auto h-4 text-[10px] font-medium leading-4 px-1.5 truncate transition-[filter,opacity]",
174+
onItemClick &&
175+
"cursor-pointer hover:brightness-95 dark:hover:brightness-110",
176+
!barStyle.backgroundColor && "bg-muted/80 text-muted-foreground",
177+
segment.continuesPrev ? "rounded-l-none ml-0" : "rounded-l-jotty ml-0.5",
178+
segment.continuesNext ? "rounded-r-none mr-0" : "rounded-r-jotty mr-0.5",
179+
segment.event.completed && "line-through opacity-70",
180+
)}
181+
style={{
182+
...barStyle,
183+
gridColumn: `${segment.colStart + 1} / span ${segment.colSpan}`,
184+
gridRow: segment.lane + 1,
185+
}}
186+
>
187+
{!segment.continuesPrev ? segment.event.title : "\u00a0"}
188+
</div>
189+
);
190+
})}
158191
</div>
159-
);
160-
})}
161-
</div>
162-
))}
192+
)}
193+
</div>
194+
);
195+
})}
163196
</div>
164197

165198
{unscheduledItems.length > 0 && (

0 commit comments

Comments
 (0)