removed tag binding overhead

This commit is contained in:
2026-08-01 08:16:39 -04:00
parent a3cf775869
commit b52a38af4d
4 changed files with 15 additions and 41 deletions
+6 -21
View File
@@ -10,7 +10,6 @@ type Tag = {
id: number;
name: string;
color: string;
ratingTypeId: number;
};
type Rating = {
@@ -119,11 +118,9 @@ function AdminPage({ user }: Props) {
const [newTagName, setNewTagName] = useState("");
const [newTagColor, setNewTagColor] = useState("#424e88");
const [newTagTypeId, setNewTagTypeId] = useState("");
const [editingTagId, setEditingTagId] = useState<number | null>(null);
const [editTagName, setEditTagName] = useState("");
const [editTagColor, setEditTagColor] = useState("#424e88");
const [editTagTypeId, setEditTagTypeId] = useState("");
const [addingTagToRating, setAddingTagToRating] = useState<number | null>(null);
const [selectedTagForRating, setSelectedTagForRating] = useState("");
const [ratingTypeFilter, setRatingTypeFilter] = useState("");
@@ -300,11 +297,11 @@ function AdminPage({ user }: Props) {
}
async function createTag() {
if (!newTagName || !newTagTypeId) return;
if (!newTagName) return;
await fetch(ua("/api/admin/tags"), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: newTagName, color: newTagColor, ratingTypeId: newTagTypeId }),
body: JSON.stringify({ name: newTagName, color: newTagColor }),
});
setNewTagName("");
setNewTagColor("#424e88");
@@ -319,11 +316,11 @@ function AdminPage({ user }: Props) {
}
async function updateTag(id: number) {
if (!editTagName || !editTagTypeId) return;
if (!editTagName) return;
await fetch(ua(`/api/admin/tags/${id}`), {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: editTagName, color: editTagColor, ratingTypeId: editTagTypeId }),
body: JSON.stringify({ name: editTagName, color: editTagColor }),
});
setEditingTagId(null);
loadTags();
@@ -591,28 +588,16 @@ function AdminPage({ user }: Props) {
<h3 style={{ margin: "0 0 8px" }}>Manage Tags</h3>
<div style={{ display: "flex", flexDirection: "column", gap: 8, marginBottom: 8 }}>
<div style={{ display: "flex", gap: 8 }}>
<select value={newTagTypeId} onChange={(e) => { setNewTagTypeId(e.target.value); }} style={{ padding: "8px 12px", fontSize: 14, border: `1px solid ${borderClr}`, background: inputBg, color: textClr }}>
<option value="">-- Rating Type --</option>
{ratingTypes.map((rt) => (
<option key={rt.id} value={rt.id}>{rt.name}</option>
))}
</select>
<input placeholder="Tag name" value={newTagName} onChange={(e) => setNewTagName(e.target.value)} style={{ flex: 1, padding: "8px 12px", fontSize: 14, border: `1px solid ${borderClr}`, background: inputBg, color: textClr }} />
<input type="color" value={newTagColor} onChange={(e) => setNewTagColor(e.target.value)} style={{ width: 40, height: 36, border: `1px solid ${borderClr}`, background: inputBg, cursor: "pointer" }} />
<button onClick={createTag} style={{ padding: "8px 16px", fontSize: 14, background: "#424e88", color: "#fff", border: "none", cursor: "pointer" }}>Add</button>
</div>
</div>
<div style={{ fontSize: 13, color: mutedClr, marginBottom: 6 }}>Tags for selected type:</div>
<div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
{tags.filter((t) => !newTagTypeId || t.ratingTypeId === Number(newTagTypeId)).map((t) => (
{tags.map((t) => (
<div key={t.id} style={{ display: "flex", alignItems: "center", gap: 4, padding: "4px 8px", background: editingTagId === t.id ? "#111" : "#000", color: t.color, fontSize: 13, fontWeight: 600 }}>
{editingTagId === t.id ? (
<>
<select value={editTagTypeId} onChange={(e) => setEditTagTypeId(e.target.value)} style={{ padding: "4px 6px", fontSize: 12, border: `1px solid ${borderClr}`, background: inputBg, color: textClr, width: 100 }}>
{ratingTypes.map((rt) => (
<option key={rt.id} value={rt.id}>{rt.name}</option>
))}
</select>
<input value={editTagName} onChange={(e) => setEditTagName(e.target.value)} style={{ width: 100, padding: "4px 6px", fontSize: 12, border: `1px solid ${borderClr}`, background: inputBg, color: textClr }} />
<input type="color" value={editTagColor} onChange={(e) => setEditTagColor(e.target.value)} style={{ width: 30, height: 24, border: `1px solid ${borderClr}`, background: inputBg, cursor: "pointer", padding: 0 }} />
<button onClick={() => updateTag(t.id)} style={{ padding: "4px 8px", fontSize: 11, background: "#424e88", color: "#fff", border: "none", cursor: "pointer" }}>Save</button>
@@ -621,7 +606,7 @@ function AdminPage({ user }: Props) {
) : (
<>
<span>{t.name}</span>
<button onClick={() => { setEditingTagId(t.id); setEditTagName(t.name); setEditTagColor(t.color); setEditTagTypeId(String(t.ratingTypeId)); }} style={{ padding: 0, background: "none", color: t.color, border: "none", cursor: "pointer", fontSize: 12, lineHeight: 1 }}>&#9998;</button>
<button onClick={() => { setEditingTagId(t.id); setEditTagName(t.name); setEditTagColor(t.color); }} style={{ padding: 0, background: "none", color: t.color, border: "none", cursor: "pointer", fontSize: 12, lineHeight: 1 }}>&#9998;</button>
<button onClick={() => { if (!window.confirm("Delete this tag?")) return; deleteTag(t.id); }} style={{ padding: 0, background: "none", color: t.color, border: "none", cursor: "pointer", fontSize: 14, lineHeight: 1 }}>&times;</button>
</>
)}
-1
View File
@@ -24,7 +24,6 @@ type Tag = {
id: number;
name: string;
color: string;
ratingTypeId: number;
};
type Review = {
+4 -7
View File
@@ -22,12 +22,10 @@ model User {
}
model Tag {
id Int @id @default(autoincrement())
name String
color String
ratingTypeId Int
ratings RatingTag[]
ratingType RatingType @relation(fields: [ratingTypeId], references: [id])
id Int @id @default(autoincrement())
name String
color String
ratings RatingTag[]
}
model RatingTag {
@@ -60,7 +58,6 @@ model RatingType {
Header Header[]
ratings Rating[]
categories RatingCategory[]
tags Tag[]
}
model Rating {
+5 -12
View File
@@ -24,7 +24,7 @@ const cache = {
userStars: new Map<number, Map<number, Set<number>>>(),
catRanks: new Map<number, Map<number, number>>(),
totalInType: new Map<number, number>(),
tags: [] as { id: number; name: string; color: string; ratingTypeId: number }[],
tags: [] as { id: number; name: string; color: string }[],
polls: [] as { id: number; name: string; description: string; image: string; completed: boolean; resultEntryId: number | null; categoryId: number | null; entries: { id: number; pollId: number; option: string; amount: number; supporters: number }[]; tags: { pollId: number; tagId: number; tag: { id: number; name: string; color: string; image: string } }[] }[],
pollCategories: [] as { id: number; name: string }[],
pollTags: [] as { id: number; name: string; color: string; image: string }[],
@@ -1061,9 +1061,8 @@ app.get("/api/tags", (_req, res) => {
app.post("/api/admin/tags", async (req, res) => {
if (!(await requireAdmin(req, res))) return;
const { name, color } = req.body;
const ratingTypeId = Number(req.body.ratingTypeId);
if (!name || !color || !ratingTypeId) { res.status(400).json({ error: "name, color, and ratingTypeId are required" }); return; }
const tag = await prisma.tag.create({ data: { name, color, ratingTypeId } });
if (!name || !color) { res.status(400).json({ error: "name and color are required" }); return; }
const tag = await prisma.tag.create({ data: { name, color } });
await rebuildCache();
res.json(tag);
});
@@ -1072,9 +1071,8 @@ app.put("/api/admin/tags/:id", async (req, res) => {
if (!(await requireAdmin(req, res))) return;
const id = Number(req.params.id);
const { name, color } = req.body;
const ratingTypeId = Number(req.body.ratingTypeId);
if (!name || !color || !ratingTypeId) { res.status(400).json({ error: "name, color, and ratingTypeId are required" }); return; }
const tag = await prisma.tag.update({ where: { id }, data: { name, color, ratingTypeId } });
if (!name || !color) { res.status(400).json({ error: "name and color are required" }); return; }
const tag = await prisma.tag.update({ where: { id }, data: { name, color } });
await rebuildCache();
res.json(tag);
});
@@ -1088,11 +1086,6 @@ app.delete("/api/admin/tags/:id", async (req, res) => {
res.json({ success: true });
});
app.get("/api/tags-by-type/:ratingTypeId", (req, res) => {
const ratingTypeId = Number(req.params.ratingTypeId);
res.json(cache.tags.filter(t => t.ratingTypeId === ratingTypeId));
});
app.post("/api/admin/ratings/:id/tags", async (req, res) => {
if (!(await requireAdmin(req, res))) return;
const id = Number(req.params.id);