removed tag binding overhead
This commit is contained in:
@@ -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 }}>✎</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 }}>✎</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 }}>×</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -24,7 +24,6 @@ type Tag = {
|
||||
id: number;
|
||||
name: string;
|
||||
color: string;
|
||||
ratingTypeId: number;
|
||||
};
|
||||
|
||||
type Review = {
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user