From b796195270772bb9852f5c086c8423cb632b6ad6 Mon Sep 17 00:00:00 2001 From: Bhasher Date: Sun, 31 Dec 2023 04:35:52 +0100 Subject: [PATCH] Select tags --- backend/db/cardstags.go | 8 +- backend/db/main.go | 2 + backend/types/cardtag.go | 8 +- frontend/package-lock.json | 40 +++------ frontend/package.json | 3 +- frontend/src/components/card.svelte | 11 ++- frontend/src/components/modal_card.svelte | 2 +- frontend/src/components/modal_tag.svelte | 90 +++++++++++++------- frontend/src/components/modal_tags.svelte | 4 +- frontend/src/components/project.svelte | 18 +++- frontend/src/components/selectProject.svelte | 4 - frontend/src/stores/interfaces.ts | 45 +++++++--- frontend/src/stores/projectTags.ts | 52 +++++++++++ frontend/src/utils/api.ts | 4 - 14 files changed, 193 insertions(+), 98 deletions(-) create mode 100644 frontend/src/stores/projectTags.ts diff --git a/backend/db/cardstags.go b/backend/db/cardstags.go index 5387a75..574a9b5 100644 --- a/backend/db/cardstags.go +++ b/backend/db/cardstags.go @@ -5,7 +5,7 @@ import ( ) func CreateCardTag(ct types.CardTag) error { - _, err := db.Exec("INSERT INTO cardtags (card_id, tag_id, value) VALUES (?, ?, ?)", ct.CardID, ct.TagID, ct.Value) + _, err := db.Exec("INSERT INTO cardtags (card_id, tag_id, option_id, value) VALUES (?, ?, ?, ?)", ct.CardID, ct.TagID, ct.OptionID, ct.Value) return err } @@ -18,7 +18,7 @@ func GetCardTags(cardID int, projectID int) ([]types.FullCardTag, error) { projectID = card.ProjectID } - rows, err := db.Query(`SELECT t.id, t.title, t.type, COALESCE(ct.value, '') + rows, err := db.Query(`SELECT t.id, t.title, t.type, COALESCE(ct.option_id, -1), COALESCE(ct.value, '') FROM tags t LEFT JOIN cardtags ct ON ct.tag_id = t.id AND ct.card_id = ? WHERE t.project_id = ? @@ -31,7 +31,7 @@ func GetCardTags(cardID int, projectID int) ([]types.FullCardTag, error) { var cardtags []types.FullCardTag for rows.Next() { ct := types.FullCardTag{CardID: cardID} - if err := rows.Scan(&ct.TagID, &ct.TagTitle, &ct.TagType, &ct.Value); err != nil { + if err := rows.Scan(&ct.TagID, &ct.TagTitle, &ct.TagType, &ct.OptionID, &ct.Value); err != nil { return nil, err } cardtags = append(cardtags, ct) @@ -61,7 +61,7 @@ func DeleteCardTags(card_id int) (int64, error) { } func UpdateCardTag(ct types.CardTag) (int64, error) { - res, err := db.Exec("UPDATE cardtags SET value = ? WHERE card_id = ? AND tag_id = ?", ct.Value, ct.CardID, ct.TagID) + res, err := db.Exec("UPDATE cardtags SET option_id = ?, value = ? WHERE card_id = ? AND tag_id = ?", ct.OptionID, ct.Value, ct.CardID, ct.TagID) if err != nil { return 0, err } diff --git a/backend/db/main.go b/backend/db/main.go index e8736ab..0dd8bfe 100644 --- a/backend/db/main.go +++ b/backend/db/main.go @@ -42,10 +42,12 @@ func InitDB(driver string, connStr string) error { CREATE TABLE IF NOT EXISTS cardtags ( card_id INTEGER, tag_id INTEGER, + option_id TEXT, value TEXT, PRIMARY KEY(card_id, tag_id), FOREIGN KEY(card_id) REFERENCES cards(id) FOREIGN KEY(tag_id) REFERENCES tags(id) + FOREIGN KEY(option_id) REFERENCES tagsoptions(id) ); CREATE TABLE IF NOT EXISTS tagsoptions ( id INTEGER PRIMARY KEY AUTOINCREMENT, diff --git a/backend/types/cardtag.go b/backend/types/cardtag.go index b981227..49fc7d6 100644 --- a/backend/types/cardtag.go +++ b/backend/types/cardtag.go @@ -1,9 +1,10 @@ package types type CardTag struct { - CardID int `json:"card_id"` - TagID int `json:"tag_id"` - Value string `json:"value"` + CardID int `json:"card_id"` + TagID int `json:"tag_id"` + OptionID int `json:"option_id"` + Value string `json:"value"` } type FullCardTag struct { @@ -11,5 +12,6 @@ type FullCardTag struct { TagID int `json:"tag_id"` TagTitle string `json:"tag_title"` TagType int `json:"tag_type"` + OptionID int `json:"option_id"` Value string `json:"value"` } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3bfbf25..7477813 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,7 +8,8 @@ "name": "frontend", "version": "0.0.1", "dependencies": { - "axios": "^1.6.3" + "axios": "^1.6.3", + "svelte-multiselect": "^10.2.0" }, "devDependencies": { "@playwright/test": "^1.28.1", @@ -44,7 +45,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", - "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -558,7 +558,6 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, "dependencies": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -572,7 +571,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -581,7 +579,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -589,14 +586,12 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.20", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", - "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -928,8 +923,7 @@ "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" }, "node_modules/@types/json-schema": { "version": "7.0.15", @@ -1158,7 +1152,6 @@ "version": "8.11.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", - "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -1238,7 +1231,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dev": true, "dependencies": { "dequal": "^2.0.3" } @@ -1271,7 +1263,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==", - "dev": true, "dependencies": { "dequal": "^2.0.3" } @@ -1389,7 +1380,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz", "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==", - "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15", "@types/estree": "^1.0.1", @@ -1460,7 +1450,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "dev": true, "dependencies": { "mdn-data": "2.0.30", "source-map-js": "^1.0.1" @@ -1525,7 +1514,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, "engines": { "node": ">=6" } @@ -1849,7 +1837,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, "dependencies": { "@types/estree": "^1.0.0" } @@ -2267,7 +2254,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", - "dev": true, "dependencies": { "@types/estree": "*" } @@ -2357,8 +2343,7 @@ "node_modules/locate-character": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "dev": true + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==" }, "node_modules/locate-path": { "version": "6.0.0", @@ -2397,7 +2382,6 @@ "version": "0.30.5", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", - "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, @@ -2408,8 +2392,7 @@ "node_modules/mdn-data": { "version": "2.0.30", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "dev": true + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" }, "node_modules/merge2": { "version": "1.4.1", @@ -2662,7 +2645,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", - "dev": true, "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^3.0.0", @@ -3115,7 +3097,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3172,7 +3153,6 @@ "version": "4.2.8", "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.8.tgz", "integrity": "sha512-hU6dh1MPl8gh6klQZwK/n73GiAHiR95IkFsesLPbMeEZi36ydaXL/ZAb4g9sayT0MXzpxyZjR28yderJHxcmYA==", - "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.1", "@jridgewell/sourcemap-codec": "^1.4.15", @@ -3253,6 +3233,14 @@ "svelte": "^3.19.0 || ^4.0.0" } }, + "node_modules/svelte-multiselect": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/svelte-multiselect/-/svelte-multiselect-10.2.0.tgz", + "integrity": "sha512-nbv0dTgSHGENbwKdiN5seFD4ljtGSAEZGcMkHfcc+Nnk7tVwM2jxvCgkRKp9FdPUKG1M6Zp8ZXLDU+xoZxHyTA==", + "dependencies": { + "svelte": "^4.2.0" + } + }, "node_modules/svelte-preprocess": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index 9993b9c..14997b5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -34,6 +34,7 @@ }, "type": "module", "dependencies": { - "axios": "^1.6.3" + "axios": "^1.6.3", + "svelte-multiselect": "^10.2.0" } } diff --git a/frontend/src/components/card.svelte b/frontend/src/components/card.svelte index f19ed36..b6f1f02 100644 --- a/frontend/src/components/card.svelte +++ b/frontend/src/components/card.svelte @@ -1,5 +1,6 @@ - - {tag.tag_title} - - - - - - - +{#if projectTag} + + {projectTag.title} + + {#if projectTag.type == 0} + + + {:else} + + {/if} + + + +{/if} diff --git a/frontend/src/components/modal_tags.svelte b/frontend/src/components/modal_tags.svelte index 80b8c87..1eccdaf 100644 --- a/frontend/src/components/modal_tags.svelte +++ b/frontend/src/components/modal_tags.svelte @@ -1,7 +1,7 @@ diff --git a/frontend/src/stores/interfaces.ts b/frontend/src/stores/interfaces.ts index ed25bc3..11b234b 100644 --- a/frontend/src/stores/interfaces.ts +++ b/frontend/src/stores/interfaces.ts @@ -10,34 +10,51 @@ export interface Card { project_id: number; title: string; content: string; - tags: Tag[]; + tags: TagValue[]; } -export interface Tag { +export interface TagValue { card_id: number; tag_id: number; - tag_title: string; + option_id: number; + value: string; +} + +export interface MeTag { + id: number; + project_id: number; + title: string; + type: number; + options: TagOption[]; +} + +export interface TagOption { + id: number; + tag_id: number; value: string; } export function parseCard (c: any) { let card: Card = c; - if (card.tags == null) card.tags = []; +if (card.tags == null) card.tags = []; return card; }; export function parseCards (cards: any) { if (cards == null) return []; + return cards.map((c: any) => parseCard(c)); +} - let cardsArray; - try { - cardsArray = JSON.parse(cards); - } catch (e) { - toastAlert('Error', 'Could not parse cards'); - return []; - } +export function parseMeTag (t: any) { + let tag: MeTag = t; + if (tag.options == null) tag.options = []; + return tag; +} - if (cardsArray == null) return []; - - return cardsArray.map(parseCard); +export function parseMeTags (tags: any) { + if (tags == null) return {}; + return tags.map(parseMeTag).reduce((acc: any, tag: MeTag) => { + acc[tag.id] = tag; + return acc; + }); } \ No newline at end of file diff --git a/frontend/src/stores/projectTags.ts b/frontend/src/stores/projectTags.ts new file mode 100644 index 0000000..b86f522 --- /dev/null +++ b/frontend/src/stores/projectTags.ts @@ -0,0 +1,52 @@ +import { get, writable } from "svelte/store"; +import type { MeTag, TagOption } from "./interfaces"; +import api, { processError } from "../utils/api"; +import status from "../utils/status"; + +const { subscribe, set, update } = writable({} as { [key: number]: MeTag }); + +export default { + subscribe, + init: async (projectID: number) : Promise => { + const response = await api.get(`/v1/projects/${projectID}/tags`); + + if (response.status !== 200) { + processError(response); + return false; + } + + const metags: MeTag[] = response.data; + + const tags: { [key: number]: MeTag } = {}; + + metags.forEach((tag: MeTag) => { + if(tag.options === null) tag.options = []; + tags[tag.id] = tag; + }); + + set(tags); + + return true; + }, + add: async (tag_id: number, value: string) => { + const response = await api.post(`/v1/tags/${tag_id}/options`, { + value + }); + + if (response.status !== status.Created) { + processError(response, 'Failed to create tag option'); + return; + } + + const option: TagOption = { + id: response.data.id, + tag_id, + value, + }; + + update(tags => { + tags[tag_id].options.push(option); + return tags; + }); + }, +} \ No newline at end of file diff --git a/frontend/src/utils/api.ts b/frontend/src/utils/api.ts index 3045105..1d07c4c 100644 --- a/frontend/src/utils/api.ts +++ b/frontend/src/utils/api.ts @@ -15,8 +15,6 @@ export function processError (response: AxiosResponse, message: string let title = `${response.status} ${response.statusText}`; let subtitle = message; - console.log(response.headers) - if(response.headers["content-type"] === "application/json") { const parsed = response.data; subtitle = parsed.error; @@ -25,7 +23,5 @@ export function processError (response: AxiosResponse, message: string } } - axios.get(backend + '/api/trace'); - toastAlert(title, subtitle); } \ No newline at end of file