Add UI/logical temporal ordering feature

This commit is contained in:
KS Jannette
2026-02-24 13:32:06 -05:00
parent 0c7baeb9d8
commit 9ba13206c5
5 changed files with 193 additions and 25 deletions

View File

@@ -1,5 +1,7 @@
import { useState, useEffect, useRef } from 'react';
import type { DragEvent } from 'react';
import { useGetStickies, useClusterStickies } from '../api/api';
import type { Sticky as StickyType } from '../types/types';
import type { Sticky as StickyType, RankedCluster } from '../types/types';
import Sticky from './sticky';
import Button from './button';
import '../styles/stickies.css';
@@ -14,10 +16,22 @@ const scoreLabel = (score: number): string => {
const Stickies = () => {
const { data: stickies, isLoading, error } = useGetStickies();
const { mutate: cluster, data: clusterResponse, isPending } = useClusterStickies();
const [rankedClusters, setRankedClusters] = useState<RankedCluster[]>([]);
const dragIndex = useRef<number | null>(null);
const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);
useEffect(() => {
if (clusterResponse?.clusters) {
setRankedClusters(
clusterResponse.clusters.map((c, i) => ({ ...c, rank: i + 1 }))
);
}
}, [clusterResponse]);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
const clusters = clusterResponse?.clusters;
const score = clusterResponse?.score;
const handleCluster = () => {
@@ -35,19 +49,69 @@ const Stickies = () => {
const renderStickies = (items: StickyType[]) =>
items?.map((sticky) => <Sticky key={sticky.id} sticky={sticky} />);
const handleDragStart = (index: number) => {
dragIndex.current = index;
};
const handleDragOver = (e: DragEvent, index: number) => {
e.preventDefault();
setDragOverIndex(index);
};
const handleDragLeave = () => {
setDragOverIndex(null);
};
const handleDrop = (targetIndex: number) => {
const sourceIndex = dragIndex.current;
if (sourceIndex === null || sourceIndex === targetIndex) {
dragIndex.current = null;
setDragOverIndex(null);
return;
}
const reordered = [...rankedClusters];
const [moved] = reordered.splice(sourceIndex, 1);
reordered.splice(targetIndex, 0, moved);
setRankedClusters(reordered.map((c, i) => ({ ...c, rank: i + 1 })));
dragIndex.current = null;
setDragOverIndex(null);
};
const handleDragEnd = () => {
dragIndex.current = null;
setDragOverIndex(null);
};
return (
<div className="stickies-container">
<Button onClick={handleCluster} isLoading={isPending} label="Group Stickies By Topic" />
{clusters ? (
{rankedClusters.length > 0 ? (
<div className="clusters-container">
{score != null && (
<div className="cohesion-score">
Cluster cohesion: <strong>{score.toFixed(2)}</strong> — {scoreLabel(score)}
</div>
)}
{clusters.map((group) => (
<div key={group.label} className="cluster-group">
<h3 className="cluster-label">{group.label}</h3>
{rankedClusters.map((group, index) => (
<div
key={group.label}
className={`cluster-group cluster-draggable${dragOverIndex === index ? ' cluster-drag-over' : ''}`}
draggable
onDragStart={() => handleDragStart(index)}
onDragOver={(e) => handleDragOver(e, index)}
onDragLeave={handleDragLeave}
onDrop={() => handleDrop(index)}
onDragEnd={handleDragEnd}
>
<div className="cluster-header">
<span className="cluster-rank" aria-label={`Priority ${group.rank}`}>
{group.rank}
</span>
<h3 className="cluster-label">{group.label}</h3>
<span className="cluster-drag-handle" aria-hidden="true">⠿</span>
</div>
<div className="stickies-grid">
{renderStickies(
group?.noteIds