Select tags

This commit is contained in:
Brieuc Dubois 2023-12-31 04:35:52 +01:00
parent 950963d7dc
commit b796195270
14 changed files with 193 additions and 98 deletions

View File

@ -5,7 +5,7 @@ import (
) )
func CreateCardTag(ct types.CardTag) error { 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 return err
} }
@ -18,7 +18,7 @@ func GetCardTags(cardID int, projectID int) ([]types.FullCardTag, error) {
projectID = card.ProjectID 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 FROM tags t
LEFT JOIN cardtags ct ON ct.tag_id = t.id AND ct.card_id = ? LEFT JOIN cardtags ct ON ct.tag_id = t.id AND ct.card_id = ?
WHERE t.project_id = ? WHERE t.project_id = ?
@ -31,7 +31,7 @@ func GetCardTags(cardID int, projectID int) ([]types.FullCardTag, error) {
var cardtags []types.FullCardTag var cardtags []types.FullCardTag
for rows.Next() { for rows.Next() {
ct := types.FullCardTag{CardID: cardID} 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 return nil, err
} }
cardtags = append(cardtags, ct) cardtags = append(cardtags, ct)
@ -61,7 +61,7 @@ func DeleteCardTags(card_id int) (int64, error) {
} }
func UpdateCardTag(ct types.CardTag) (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 { if err != nil {
return 0, err return 0, err
} }

View File

@ -42,10 +42,12 @@ func InitDB(driver string, connStr string) error {
CREATE TABLE IF NOT EXISTS cardtags ( CREATE TABLE IF NOT EXISTS cardtags (
card_id INTEGER, card_id INTEGER,
tag_id INTEGER, tag_id INTEGER,
option_id TEXT,
value TEXT, value TEXT,
PRIMARY KEY(card_id, tag_id), PRIMARY KEY(card_id, tag_id),
FOREIGN KEY(card_id) REFERENCES cards(id) FOREIGN KEY(card_id) REFERENCES cards(id)
FOREIGN KEY(tag_id) REFERENCES tags(id) FOREIGN KEY(tag_id) REFERENCES tags(id)
FOREIGN KEY(option_id) REFERENCES tagsoptions(id)
); );
CREATE TABLE IF NOT EXISTS tagsoptions ( CREATE TABLE IF NOT EXISTS tagsoptions (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,

View File

@ -1,9 +1,10 @@
package types package types
type CardTag struct { type CardTag struct {
CardID int `json:"card_id"` CardID int `json:"card_id"`
TagID int `json:"tag_id"` TagID int `json:"tag_id"`
Value string `json:"value"` OptionID int `json:"option_id"`
Value string `json:"value"`
} }
type FullCardTag struct { type FullCardTag struct {
@ -11,5 +12,6 @@ type FullCardTag struct {
TagID int `json:"tag_id"` TagID int `json:"tag_id"`
TagTitle string `json:"tag_title"` TagTitle string `json:"tag_title"`
TagType int `json:"tag_type"` TagType int `json:"tag_type"`
OptionID int `json:"option_id"`
Value string `json:"value"` Value string `json:"value"`
} }

View File

@ -8,7 +8,8 @@
"name": "frontend", "name": "frontend",
"version": "0.0.1", "version": "0.0.1",
"dependencies": { "dependencies": {
"axios": "^1.6.3" "axios": "^1.6.3",
"svelte-multiselect": "^10.2.0"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.28.1", "@playwright/test": "^1.28.1",
@ -44,7 +45,6 @@
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
"integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==",
"dev": true,
"dependencies": { "dependencies": {
"@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9" "@jridgewell/trace-mapping": "^0.3.9"
@ -558,7 +558,6 @@
"version": "0.3.3", "version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
"dev": true,
"dependencies": { "dependencies": {
"@jridgewell/set-array": "^1.0.1", "@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/sourcemap-codec": "^1.4.10",
@ -572,7 +571,6 @@
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
"integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
"dev": true,
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"
} }
@ -581,7 +579,6 @@
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
"dev": true,
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"
} }
@ -589,14 +586,12 @@
"node_modules/@jridgewell/sourcemap-codec": { "node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15", "version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
"dev": true
}, },
"node_modules/@jridgewell/trace-mapping": { "node_modules/@jridgewell/trace-mapping": {
"version": "0.3.20", "version": "0.3.20",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz",
"integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==",
"dev": true,
"dependencies": { "dependencies": {
"@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
@ -928,8 +923,7 @@
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
"dev": true
}, },
"node_modules/@types/json-schema": { "node_modules/@types/json-schema": {
"version": "7.0.15", "version": "7.0.15",
@ -1158,7 +1152,6 @@
"version": "8.11.2", "version": "8.11.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
"integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==",
"dev": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@ -1238,7 +1231,6 @@
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
"dev": true,
"dependencies": { "dependencies": {
"dequal": "^2.0.3" "dequal": "^2.0.3"
} }
@ -1271,7 +1263,6 @@
"version": "3.2.1", "version": "3.2.1",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz",
"integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==", "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==",
"dev": true,
"dependencies": { "dependencies": {
"dequal": "^2.0.3" "dequal": "^2.0.3"
} }
@ -1389,7 +1380,6 @@
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz", "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz",
"integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==", "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==",
"dev": true,
"dependencies": { "dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15", "@jridgewell/sourcemap-codec": "^1.4.15",
"@types/estree": "^1.0.1", "@types/estree": "^1.0.1",
@ -1460,7 +1450,6 @@
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
"integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
"dev": true,
"dependencies": { "dependencies": {
"mdn-data": "2.0.30", "mdn-data": "2.0.30",
"source-map-js": "^1.0.1" "source-map-js": "^1.0.1"
@ -1525,7 +1514,6 @@
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
"dev": true,
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
@ -1849,7 +1837,6 @@
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
"dev": true,
"dependencies": { "dependencies": {
"@types/estree": "^1.0.0" "@types/estree": "^1.0.0"
} }
@ -2267,7 +2254,6 @@
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
"integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
"dev": true,
"dependencies": { "dependencies": {
"@types/estree": "*" "@types/estree": "*"
} }
@ -2357,8 +2343,7 @@
"node_modules/locate-character": { "node_modules/locate-character": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="
"dev": true
}, },
"node_modules/locate-path": { "node_modules/locate-path": {
"version": "6.0.0", "version": "6.0.0",
@ -2397,7 +2382,6 @@
"version": "0.30.5", "version": "0.30.5",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz",
"integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==",
"dev": true,
"dependencies": { "dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15" "@jridgewell/sourcemap-codec": "^1.4.15"
}, },
@ -2408,8 +2392,7 @@
"node_modules/mdn-data": { "node_modules/mdn-data": {
"version": "2.0.30", "version": "2.0.30",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="
"dev": true
}, },
"node_modules/merge2": { "node_modules/merge2": {
"version": "1.4.1", "version": "1.4.1",
@ -2662,7 +2645,6 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
"integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==",
"dev": true,
"dependencies": { "dependencies": {
"@types/estree": "^1.0.0", "@types/estree": "^1.0.0",
"estree-walker": "^3.0.0", "estree-walker": "^3.0.0",
@ -3115,7 +3097,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -3172,7 +3153,6 @@
"version": "4.2.8", "version": "4.2.8",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.8.tgz", "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.8.tgz",
"integrity": "sha512-hU6dh1MPl8gh6klQZwK/n73GiAHiR95IkFsesLPbMeEZi36ydaXL/ZAb4g9sayT0MXzpxyZjR28yderJHxcmYA==", "integrity": "sha512-hU6dh1MPl8gh6klQZwK/n73GiAHiR95IkFsesLPbMeEZi36ydaXL/ZAb4g9sayT0MXzpxyZjR28yderJHxcmYA==",
"dev": true,
"dependencies": { "dependencies": {
"@ampproject/remapping": "^2.2.1", "@ampproject/remapping": "^2.2.1",
"@jridgewell/sourcemap-codec": "^1.4.15", "@jridgewell/sourcemap-codec": "^1.4.15",
@ -3253,6 +3233,14 @@
"svelte": "^3.19.0 || ^4.0.0" "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": { "node_modules/svelte-preprocess": {
"version": "5.1.3", "version": "5.1.3",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.3.tgz", "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.3.tgz",

View File

@ -34,6 +34,7 @@
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"axios": "^1.6.3" "axios": "^1.6.3",
"svelte-multiselect": "^10.2.0"
} }
} }

View File

@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { Card } from '../stores/interfaces'; import { type Card, type MeTag } from '../stores/interfaces';
import projectTags from '../stores/projectTags';
import ModalCard from './modal_card.svelte'; import ModalCard from './modal_card.svelte';
export let card: Card; export let card: Card;
@ -33,7 +34,13 @@
{#if card.tags} {#if card.tags}
<div class="tags"> <div class="tags">
{#each card.tags as tag} {#each card.tags as tag}
{#if tag.value} {#if tag.option_id && tag.option_id !== -1}
{#if $projectTags[tag.tag_id]}
<span class="tag" style="border: 1px solid #333"
>{$projectTags[tag.tag_id]?.options.find((o) => o.id == tag.option_id)?.value}</span
>
{/if}
{:else if tag.value}
<span class="tag" style="border: 1px solid #333">{tag.value}</span> <span class="tag" style="border: 1px solid #333">{tag.value}</span>
{/if} {/if}
{/each} {/each}

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import ModalTags from './modal_tags.svelte'; import ModalTags from './modal_tags.svelte';
import type { Card } from '../stores/interfaces'; import type { Card, MeTag } from '../stores/interfaces';
import api, { processError } from '../utils/api'; import api, { processError } from '../utils/api';
import status from '../utils/status'; import status from '../utils/status';

View File

@ -1,17 +1,20 @@
<script lang="ts"> <script lang="ts">
import type { Tag } from '../stores/interfaces'; import type { TagValue, MeTag } from '../stores/interfaces';
import projectTags from '../stores/projectTags';
import api, { processError } from '../utils/api'; import api, { processError } from '../utils/api';
import status from '../utils/status'; import status from '../utils/status';
export let tag: Tag; export let tag: TagValue;
let newValue: string = tag.value;
export let removeTag: (id: number) => void; export let removeTag: (id: number) => void;
let newValue: string = tag.value;
let newOption: number = tag.option_id;
let projectTag = $projectTags[tag.tag_id];
async function saveTag() { async function saveTag() {
if (tag.value === newValue) return; if (tag.value === newValue && tag.option_id == newOption) return;
// DELETE // DELETE
if (tag.value !== '' && newValue === '') { if ((tag.value !== '' && newValue === '') || (tag.option_id !== -1 && newOption === -1)) {
const response = await api.delete(`/v1/cards/${tag.card_id}/tags/${tag.tag_id}`); const response = await api.delete(`/v1/cards/${tag.card_id}/tags/${tag.tag_id}`);
if (response.status !== status.NoContent) { if (response.status !== status.NoContent) {
@ -20,9 +23,10 @@
} }
} }
// CREATE // CREATE
else if (tag.value === '' && newValue !== '') { else if ((tag.value === '' && newValue !== '') || (tag.option_id == -1 && newOption !== -1)) {
const response = await api.post(`/v1/cards/${tag.card_id}/tags/${tag.tag_id}`, { const response = await api.post(`/v1/cards/${tag.card_id}/tags/${tag.tag_id}`, {
value: newValue value: newValue,
option_id: newOption
}); });
if (response.status !== status.Created) { if (response.status !== status.Created) {
processError(response, 'Failed to create tag'); processError(response, 'Failed to create tag');
@ -32,7 +36,8 @@
// UPDATE // UPDATE
else { else {
const response = await api.put(`/v1/cards/${tag.card_id}/tags/${tag.tag_id}`, { const response = await api.put(`/v1/cards/${tag.card_id}/tags/${tag.tag_id}`, {
value: newValue value: newValue,
option_id: newOption
}); });
if (response.status !== status.NoContent) { if (response.status !== status.NoContent) {
processError(response, 'Failed to update tag'); processError(response, 'Failed to update tag');
@ -41,30 +46,49 @@
} }
tag.value = newValue; tag.value = newValue;
tag.option_id = newOption;
}
async function newTagOption() {
const value = prompt('New option value');
if (value) projectTags.add(tag.tag_id, value);
} }
</script> </script>
<tr class="tag"> {#if projectTag}
<td class="tag-title">{tag.tag_title}</td> <tr class="tag">
<td> <td class="tag-title">{projectTag.title}</td>
<input bind:value={newValue} on:blur={saveTag} /> <td>
</td> {#if projectTag.type == 0}
<td> <select bind:value={newOption} on:change={saveTag}>
<button on:click={() => removeTag(tag.tag_id)} class="remove-tag-button"> <option value={-1}></option>
<svg {#each projectTag.options as option}
xmlns="http://www.w3.org/2000/svg" <option value={option.id}>{option.value}</option>
width="16" {/each}
height="16" </select>
viewBox="0 0 24 24" <button style="color: white; font-size: 20px;" on:click={newTagOption}>+</button>
fill="none" {:else}
stroke="white" <input bind:value={newValue} on:blur={saveTag} />
stroke-width="2" {/if}
stroke-linecap="round" </td>
stroke-linejoin="round" <!-- <td>
> <button on:click={() => removeTag(tag.tag_id)} class="remove-tag-button">
<line x1="18" y1="6" x2="6" y2="18"></line> <svg
<line x1="6" y1="6" x2="18" y2="18"></line> xmlns="http://www.w3.org/2000/svg"
</svg> width="16"
</button> height="16"
</td> viewBox="0 0 24 24"
</tr> fill="none"
stroke="white"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</td> -->
</tr>
{/if}

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import ModalTag from './modal_tag.svelte'; import ModalTag from './modal_tag.svelte';
import status from '../utils/status'; import status from '../utils/status';
import type { Card } from '../stores/interfaces'; import type { Card, MeTag } from '../stores/interfaces';
import api, { processError } from '../utils/api'; import api, { processError } from '../utils/api';
export let card: Card; export let card: Card;
@ -23,7 +23,7 @@
} }
const id = response.data.id; const id = response.data.id;
card.tags = [...card.tags, { card_id: card.id, tag_id: id, tag_title: newTagName, value: '' }]; card.tags = [...card.tags, { card_id: card.id, tag_id: id, option_id: -1, value: '' }];
newTagName = ''; newTagName = '';
} }

View File

@ -1,9 +1,16 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import CardC from './card.svelte'; import CardC from './card.svelte';
import { type Project, type Card, parseCards } from '../stores/interfaces'; import {
type Project,
type Card,
parseCards,
type MeTag,
parseMeTags
} from '../stores/interfaces';
import status from '../utils/status'; import status from '../utils/status';
import api, { processError } from '../utils/api'; import api, { processError } from '../utils/api';
import projectTags from '../stores/projectTags';
export let projectId: number; export let projectId: number;
@ -20,15 +27,18 @@
project = response.data; project = response.data;
response = await api.get(`/v1/projects/${projectId}/cards`, { response = await api.get(`/v1/projects/${projectId}/cards`);
validateStatus: () => true
});
if (response.status === status.OK) { if (response.status === status.OK) {
cards = parseCards(response.data); cards = parseCards(response.data);
} else { } else {
cards = []; cards = [];
processError(response, 'Failed to fetch cards'); processError(response, 'Failed to fetch cards');
return;
}
if (!(await projectTags.init(projectId))) {
return;
} }
}); });

View File

@ -26,12 +26,8 @@
return; return;
} }
console.log(newTitle);
project.title = newTitle; project.title = newTitle;
console.log(project.title);
edit = false; edit = false;
} }
</script> </script>

View File

@ -10,34 +10,51 @@ export interface Card {
project_id: number; project_id: number;
title: string; title: string;
content: string; content: string;
tags: Tag[]; tags: TagValue[];
} }
export interface Tag { export interface TagValue {
card_id: number; card_id: number;
tag_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; value: string;
} }
export function parseCard (c: any) { export function parseCard (c: any) {
let card: Card = c; let card: Card = c;
if (card.tags == null) card.tags = []; if (card.tags == null) card.tags = [];
return card; return card;
}; };
export function parseCards (cards: any) { export function parseCards (cards: any) {
if (cards == null) return []; if (cards == null) return [];
return cards.map((c: any) => parseCard(c));
}
let cardsArray; export function parseMeTag (t: any) {
try { let tag: MeTag = t;
cardsArray = JSON.parse(cards); if (tag.options == null) tag.options = [];
} catch (e) { return tag;
toastAlert('Error', 'Could not parse cards'); }
return [];
}
if (cardsArray == null) return []; export function parseMeTags (tags: any) {
if (tags == null) return {};
return cardsArray.map(parseCard); return tags.map(parseMeTag).reduce((acc: any, tag: MeTag) => {
acc[tag.id] = tag;
return acc;
});
} }

View File

@ -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<boolean> => {
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;
});
},
}

View File

@ -15,8 +15,6 @@ export function processError (response: AxiosResponse<any, any>, message: string
let title = `${response.status} ${response.statusText}`; let title = `${response.status} ${response.statusText}`;
let subtitle = message; let subtitle = message;
console.log(response.headers)
if(response.headers["content-type"] === "application/json") { if(response.headers["content-type"] === "application/json") {
const parsed = response.data; const parsed = response.data;
subtitle = parsed.error; subtitle = parsed.error;
@ -25,7 +23,5 @@ export function processError (response: AxiosResponse<any, any>, message: string
} }
} }
axios.get(backend + '/api/trace');
toastAlert(title, subtitle); toastAlert(title, subtitle);
} }