From 29ab75217037cbc4b04605597b194f5efd106378 Mon Sep 17 00:00:00 2001 From: Bhasher Date: Tue, 2 Jan 2024 21:13:44 +0100 Subject: [PATCH] Tag selection --- backend/db/cardstags.go | 13 +- frontend/src/api/cards.ts | 2 +- .../src/components/icons/trashIcon.svelte | 8 +- .../components/project/card/modal_card.svelte | 20 +- .../components/project/card/modal_tag.svelte | 79 +----- .../card/modal_tag/modal_new_tag.svelte | 95 +++++++ .../{ => modal_tag}/modal_tag_title.svelte | 30 ++- .../card/modal_tag/modal_tag_types.svelte | 78 ++++++ .../card/modal_tag/modal_tag_value.svelte | 19 ++ .../project/card/modal_tag/select_tags.svelte | 232 ++++++++++++++++++ .../components/project/card/modal_tags.svelte | 55 +---- .../src/components/project/project.svelte | 4 +- frontend/src/components/sidebar.svelte | 1 - frontend/src/stores/interfaces.ts | 2 +- frontend/src/stores/projectTags.ts | 20 ++ frontend/src/utils/tagTypes.ts | 26 ++ 16 files changed, 533 insertions(+), 151 deletions(-) create mode 100644 frontend/src/components/project/card/modal_tag/modal_new_tag.svelte rename frontend/src/components/project/card/{ => modal_tag}/modal_tag_title.svelte (78%) create mode 100644 frontend/src/components/project/card/modal_tag/modal_tag_types.svelte create mode 100644 frontend/src/components/project/card/modal_tag/modal_tag_value.svelte create mode 100644 frontend/src/components/project/card/modal_tag/select_tags.svelte create mode 100644 frontend/src/utils/tagTypes.ts diff --git a/backend/db/cardstags.go b/backend/db/cardstags.go index 574a9b5..ed6d054 100644 --- a/backend/db/cardstags.go +++ b/backend/db/cardstags.go @@ -18,10 +18,15 @@ 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.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 = ? + // 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 = ? + // `, cardID, projectID) + rows, err := db.Query(`SELECT t.id, t.title, t.type, ct.option_id, ct.value + FROM cardtags ct + INNER JOIN tags t ON t.id = ct.tag_id + WHERE ct.card_id = ? AND t.project_id = ? `, cardID, projectID) if err != nil { return nil, err diff --git a/frontend/src/api/cards.ts b/frontend/src/api/cards.ts index 7a5ef3b..fc8819e 100644 --- a/frontend/src/api/cards.ts +++ b/frontend/src/api/cards.ts @@ -20,7 +20,7 @@ export async function newCardApi(projectId: number, tags: TagValue[]): Promise + export let size: number = 24; + + - import type { TagValue } from '../../../stores/interfaces'; - import projectTags from '../../../stores/projectTags'; - import { cards } from '../../../stores/smallStore'; - import api, { processError } from '../../../utils/api'; - import status from '../../../utils/status'; - import ModalTagTitle from './modal_tag_title.svelte'; + import type { Card, MeTag, TagValue } from '../../../stores/interfaces'; + import ModalTagTitle from './modal_tag/modal_tag_title.svelte'; + import ModalTagValue from './modal_tag/modal_tag_value.svelte'; - export let tag: TagValue; - // export let removeTag: (id: number) => void; - let newValue: string = tag.value; - let newOption: number = tag.option_id; - - async function saveTag() { - if (tag.value === newValue && tag.option_id == newOption) return; - // DELETE - if ((tag.value !== '' && newValue === '') || (tag.option_id !== -1 && newOption === -1)) { - const response = await api.delete(`/v1/cards/${tag.card_id}/tags/${tag.tag_id}`); - - if (response.status !== status.NoContent) { - processError(response, 'Failed to delete tag'); - return; - } - } - // CREATE - 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}`, { - value: newValue, - option_id: newOption - }); - if (response.status !== status.Created) { - processError(response, 'Failed to create tag'); - return; - } - } - // UPDATE - else { - const response = await api.put(`/v1/cards/${tag.card_id}/tags/${tag.tag_id}`, { - value: newValue, - option_id: newOption - }); - if (response.status !== status.NoContent) { - processError(response, 'Failed to update tag'); - return; - } - } - - tag.value = newValue; - tag.option_id = newOption; - cards.reload(); - } - - async function newTagOption() { - const value = prompt('New option value'); - - if (value) projectTags.addOption(tag.tag_id, value); - } - - const projectTag = $projectTags[tag.tag_id]; + export let projectTag: MeTag; + export let tagValue: TagValue | undefined; + export let card: Card; {#if projectTag}
- - - {#if projectTag.type == 0} - - - {:else} - - {/if} - + +
{/if} diff --git a/frontend/src/components/project/card/modal_tag/modal_new_tag.svelte b/frontend/src/components/project/card/modal_tag/modal_new_tag.svelte new file mode 100644 index 0000000..054b77e --- /dev/null +++ b/frontend/src/components/project/card/modal_tag/modal_new_tag.svelte @@ -0,0 +1,95 @@ + + +
+ + +
{ + isOpen = false; + }} + > + + + + + + diff --git a/frontend/src/components/project/card/modal_tag_title.svelte b/frontend/src/components/project/card/modal_tag/modal_tag_title.svelte similarity index 78% rename from frontend/src/components/project/card/modal_tag_title.svelte rename to frontend/src/components/project/card/modal_tag/modal_tag_title.svelte index dba5e7e..3f74e17 100644 --- a/frontend/src/components/project/card/modal_tag_title.svelte +++ b/frontend/src/components/project/card/modal_tag/modal_tag_title.svelte @@ -1,9 +1,10 @@ + + + + + diff --git a/frontend/src/components/project/card/modal_tag/modal_tag_value.svelte b/frontend/src/components/project/card/modal_tag/modal_tag_value.svelte new file mode 100644 index 0000000..1162394 --- /dev/null +++ b/frontend/src/components/project/card/modal_tag/modal_tag_value.svelte @@ -0,0 +1,19 @@ + + + + {#if tagType?.hasOptions} + + {:else if !tagType?.hasOptions} + + {/if} + diff --git a/frontend/src/components/project/card/modal_tag/select_tags.svelte b/frontend/src/components/project/card/modal_tag/select_tags.svelte new file mode 100644 index 0000000..bc44559 --- /dev/null +++ b/frontend/src/components/project/card/modal_tag/select_tags.svelte @@ -0,0 +1,232 @@ + + +
(isOpen = !isOpen)} + tabindex="0" + role="button" + on:keydown={(e) => { + if (e.key === 'Enter') { + isOpen = !isOpen; + } + }} +> +
+ {#if tagValue} + + {tagOption?.value} + + + {/if} +
+
+ { + isOpen = false; + }} +> + {#each projectTag.options as option} +
selectOption(option.id)} + tabindex="0" + role="button" + on:keydown={(e) => { + if (e.key === 'Enter') { + selectOption(option.id); + } + }} + > + {option.value} + +
+ {/each} +
+ { + if (e.key === 'Enter') { + createOption(); + } + }} + bind:value={newOption} + /> + +
+
+ + diff --git a/frontend/src/components/project/card/modal_tags.svelte b/frontend/src/components/project/card/modal_tags.svelte index a0ee2bb..01ba14d 100644 --- a/frontend/src/components/project/card/modal_tags.svelte +++ b/frontend/src/components/project/card/modal_tags.svelte @@ -1,58 +1,21 @@ {#if card.tags} - {#each card.tags as tag} - + {#each Object.values($projectTags) as projectTag} + t.tag_id === projectTag.id)} + bind:projectTag + {card} + /> {/each} {/if} - - - - +
-
diff --git a/frontend/src/components/project/project.svelte b/frontend/src/components/project/project.svelte index 13e9c21..78c178b 100644 --- a/frontend/src/components/project/project.svelte +++ b/frontend/src/components/project/project.svelte @@ -41,7 +41,9 @@ tag_id: view.primary_tag_id, value: `No ${$projectTags[view.primary_tag_id].title}` }} - columnCards={$cards.filter((c) => c.tags.find((t) => t.tag_id)?.option_id == -1 || false)} + columnCards={$cards.filter( + (c) => !c.tags.map((t) => t.tag_id).includes(view?.primary_tag_id || -2) + )} projectId={project.id} editable={false} /> diff --git a/frontend/src/components/sidebar.svelte b/frontend/src/components/sidebar.svelte index 0de2b1e..7ae4a57 100644 --- a/frontend/src/components/sidebar.svelte +++ b/frontend/src/components/sidebar.svelte @@ -13,7 +13,6 @@ let viewEditValue: string; onMount(async () => { - console.log('aaa'); await views.init(project.id); if ($views.length > 0) currentView.set($views[0]); diff --git a/frontend/src/stores/interfaces.ts b/frontend/src/stores/interfaces.ts index 850afe1..434c000 100644 --- a/frontend/src/stores/interfaces.ts +++ b/frontend/src/stores/interfaces.ts @@ -5,7 +5,7 @@ export interface Project { export interface Card { id: number; - projectId: number; + project_id: number; title: string; content: string; tags: TagValue[]; diff --git a/frontend/src/stores/projectTags.ts b/frontend/src/stores/projectTags.ts index f1e76d3..ec25c2e 100644 --- a/frontend/src/stores/projectTags.ts +++ b/frontend/src/stores/projectTags.ts @@ -2,6 +2,7 @@ import { get, writable } from 'svelte/store'; import type { MeTag, TagOption } from './interfaces'; import api, { processError } from '../utils/api'; import status from '../utils/status'; +import { cards } from './smallStore'; const { subscribe, set, update } = writable({} as { [key: number]: MeTag }); @@ -49,6 +50,25 @@ export default { return tags; }); }, + deleteOption: async (tag_id: number, option_id: number) => { + const response = await api.delete(`/v1/tags/${tag_id}/options/${option_id}`); + + if (response.status !== status.NoContent) { + processError(response, 'Failed to delete tag option'); + return; + } + + update((tags) => { + tags[tag_id].options = tags[tag_id].options.filter((option) => option.id !== option_id); + return tags; + }); + + for (const card of get(cards)) { + //TODO: same in db + card.tags.filter((tag) => tag.tag_id !== tag_id || tag.option_id !== option_id); + } + cards.reload(); + }, delete: async (tag_id: number) => { const response = await api.delete(`/v1/tags/${tag_id}`); diff --git a/frontend/src/utils/tagTypes.ts b/frontend/src/utils/tagTypes.ts new file mode 100644 index 0000000..fc31eaf --- /dev/null +++ b/frontend/src/utils/tagTypes.ts @@ -0,0 +1,26 @@ +export const tagTypes: { [name: string]: { id: number; name: string; hasOptions: boolean } } = { + SELECT: { + id: 0, + name: 'Select', + hasOptions: true + }, + MULTISELECT: { + id: 1, + name: 'Multiselect', + hasOptions: true + }, + TEXT: { + id: 2, + name: 'Text', + hasOptions: false + } +}; + +export const getTagTypeFromId = (id: number) => { + for (let type in tagTypes) { + if (tagTypes[type].id === id) { + return tagTypes[type]; + } + } + return null; +};