Skip to content

Commit

Permalink
frontend: improve taglist styling, add filter and sorting
Browse files Browse the repository at this point in the history
  • Loading branch information
NickSavage committed Dec 20, 2024
1 parent 51f9d10 commit b93f0aa
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 46 deletions.
82 changes: 63 additions & 19 deletions zettelkasten-front/src/components/tags/TagList.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,75 @@
import React from "react";
import React, { useState } from "react";
import { useTagContext } from "../../contexts/TagContext";
import { TagListItem } from "./TagListItem";

interface TagListInterface {}

type SortOption = "name" | "name-desc" | "tasks-asc" | "tasks-desc" | "cards-asc" | "cards-desc";

export function TagList({}: TagListInterface) {
const { tags } = useTagContext();
const [sortOption, setSortOption] = useState<SortOption>("name");
const [filterText, setFilterText] = useState<string>("");

const filteredTags = (tags || []).filter(tag =>
tag.name.toLowerCase().includes(filterText.toLowerCase())
);

const sortedTags = [...filteredTags].sort((a, b) => {
switch (sortOption) {
case "name":
return a.name.localeCompare(b.name);
case "name-desc":
return b.name.localeCompare(a.name);
case "tasks-asc":
return a.task_count - b.task_count;
case "tasks-desc":
return b.task_count - a.task_count;
case "cards-asc":
return a.card_count - b.card_count;
case "cards-desc":
return b.card_count - a.card_count;
default:
return 0;
}
});

return (
<div className="pt-4">
<span className="text-lg font-bold align-center">Tags</span>
<div>
<ul>
<li>
<div className="w-full px-4 py-2 flex font-bold">
<div className="flex-grow">Name</div>
<div className="flex">
<div className="px-2">Tasks</div>
<div className="px-2">Cards</div>
</div>
</div>
</li>
{tags &&
tags.map((tag) => {
return <TagListItem tag={tag} />;
})}
</ul>
<div className="p-4 max-w-full">
<h2 className="text-2xl font-bold mb-4">Tags</h2>
<div className="mb-4 flex flex-wrap items-center gap-4">
<div className="flex-grow">
<label className="mr-2">Filter:</label>
<input
type="text"
value={filterText}
onChange={(e) => setFilterText(e.target.value)}
className="border border-gray-300 rounded px-2 py-1 w-full md:w-auto"
placeholder="Filter by name"
/>
</div>
<div className="min-w-[150px]">
<label className="mr-2">Sort by:</label>
<select
value={sortOption}
onChange={(e) => setSortOption(e.target.value as SortOption)}
className="border border-gray-300 rounded px-2 py-1 w-full"
>
<option value="name">A-Z</option>
<option value="name-desc">Z-A</option>
<option value="tasks-asc">Least Tasks</option>
<option value="tasks-desc">Most Tasks</option>
<option value="cards-asc">Least Cards</option>
<option value="cards-desc">Most Cards</option>
</select>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{sortedTags.length > 0 ? (
sortedTags.map((tag) => <TagListItem key={tag.id} tag={tag} />)
) : (
<div className="text-center text-gray-500">No tags available</div>
)}
</div>
</div>
);
Expand Down
63 changes: 36 additions & 27 deletions zettelkasten-front/src/components/tags/TagListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import { Tag } from "../../models/Tags";
import { deleteTag } from "../../api/tags";

import { useTagContext } from "../../contexts/TagContext";

interface TagListItemInterface {
Expand All @@ -12,54 +11,64 @@ interface TagListItemInterface {
export function TagListItem({ tag }: TagListItemInterface) {
const [showMenu, setShowMenu] = useState<boolean>(false);
const { setRefreshTags } = useTagContext();

const navigate = useNavigate();

function toggleMenu() {
setShowMenu(!showMenu);
}

function handleViewCards() {
let searchTerm = "#" + tag.name
navigate(`/app/search?term=${encodeURIComponent(searchTerm)}`)
let searchTerm = "#" + tag.name;
navigate(`/app/search?term=${encodeURIComponent(searchTerm)}`);
}

function handleViewTasks() {
let searchTerm = "#" + tag.name
navigate(`/app/tasks?term=${encodeURIComponent(searchTerm)}`)
let searchTerm = "#" + tag.name;
navigate(`/app/tasks?term=${encodeURIComponent(searchTerm)}`);
}

async function handleDelete() {
let _ = await deleteTag(tag.id)
.then((data) => {
setRefreshTags(true);
})
.catch((error) =>
alert(
"Unable to delete tag, make sure no cards or tasks are using it first.",
),
"Unable to delete tag, make sure no cards or tasks are using it first."
)
);
setShowMenu(false);
}

return (
<li>
<div className="w-full px-4 flex">
<div className="flex-grow">{tag.name}</div>
<div className="flex">
<div className="px-4 cursor-pointer" onClick={handleViewTasks}>{tag.task_count}</div>
<div className="px-4 cursor-pointer" onClick={handleViewCards}>{tag.card_count}</div>
</div>
<div className="dropdown">
<button onClick={toggleMenu} className="menu-button">
<div className="bg-white rounded-lg shadow-md p-4 hover:shadow-lg transition-all cursor-pointer">
<div className="flex justify-between items-center mb-2">
<h3 className="text-lg font-semibold">{tag.name}</h3>
<button onClick={toggleMenu} className="text-gray-600 hover:text-blue-600">
</button>
</div>
<div className="flex justify-between items-center text-sm text-gray-500">
<span className="cursor-pointer" onClick={handleViewTasks}>
Tasks: {tag.task_count}
</span>
<span className="cursor-pointer" onClick={handleViewCards}>
Cards: {tag.card_count}
</span>
</div>
{showMenu && (
<div className="popup-menu mt-2">
<button onClick={handleViewCards} className="block w-full text-left px-4 py-2 hover:bg-gray-100">
View Cards
</button>
<button onClick={handleViewTasks} className="block w-full text-left px-4 py-2 hover:bg-gray-100">
View Tasks
</button>
<button onClick={handleDelete} className="block w-full text-left px-4 py-2 hover:bg-gray-100 text-red-600">
Delete
</button>
{showMenu && (
<div className="popup-menu">
<button onClick={() => handleViewCards()}>View Cards</button>
<button onClick={() => handleViewTasks()}>View Tasks</button>
<button onClick={() => handleDelete()}>Delete</button>
</div>
)}
</div>
</div>
</li>
)}
</div>
);
}

0 comments on commit b93f0aa

Please sign in to comment.