Fix modalCard

This commit is contained in:
Brieuc Dubois 2024-01-09 02:06:11 +01:00
parent 5fa653f401
commit 7aee5b03bb
13 changed files with 173 additions and 144 deletions

View File

@ -17,11 +17,16 @@ async function create(projectId: number): Promise<number | null> {
return response.data.id; return response.data.id;
} }
async function update(card: Card): Promise<boolean> { async function update(
const response = await api.put(`/v1/cards/${card.id}`, { cardId: number,
project_id: card.project_id, projectId: number,
title: card.title, title: string,
content: card.content content: string
): Promise<boolean> {
const response = await api.put(`/v1/cards/${cardId}`, {
project_id: projectId,
title: title,
content: content
}); });
if (response.status !== status.NoContent) { if (response.status !== status.NoContent) {

View File

@ -4,9 +4,9 @@ import TagOption from '$lib/types/TagOption';
import api, { processError } from '$lib/utils/api'; import api, { processError } from '$lib/utils/api';
import status from '$lib/utils/status'; import status from '$lib/utils/status';
async function create(project: Project, title: string, type: number): Promise<number | null> { async function create(projectId: number, title: string, type: number): Promise<number | null> {
const response = await api.post(`/v1/tags/`, { const response = await api.post(`/v1/tags/`, {
project_id: project.id, project_id: projectId,
title, title,
type type
}); });
@ -19,8 +19,11 @@ async function create(project: Project, title: string, type: number): Promise<nu
return response.data.id; return response.data.id;
} }
async function update(tag: ProjectTag): Promise<boolean> { async function update(tagId: number, title: string, type: number): Promise<boolean> {
const response = await api.put(`/v1/tags/${tag.id}`, tag); const response = await api.put(`/v1/tags/${tagId}`, {
title,
type
});
if (response.status !== status.NoContent) { if (response.status !== status.NoContent) {
processError(response, 'Failed to update tag'); processError(response, 'Failed to update tag');

View File

@ -2,7 +2,7 @@
import currentDraggedCard from '$lib/stores/currentDraggedCard'; import currentDraggedCard from '$lib/stores/currentDraggedCard';
import currentModalCard from '$lib/stores/currentModalCard'; import currentModalCard from '$lib/stores/currentModalCard';
import type Card from '$lib/types/Card'; import type Card from '$lib/types/Card';
// import ModalCard from './ModalCard.svelte'; import ModalCard from './ModalCard.svelte';
export let card: Card; export let card: Card;
</script> </script>
@ -34,7 +34,7 @@
</div> </div>
</div> </div>
<!-- <ModalCard bind:card /> --> <ModalCard {card} />
<style lang="less"> <style lang="less">
.card { .card {

View File

@ -2,9 +2,9 @@
import CloseIcon from '$lib/components/icons/CloseIcon.svelte'; import CloseIcon from '$lib/components/icons/CloseIcon.svelte';
import TrashIcon from '$lib/components/icons/TrashIcon.svelte'; import TrashIcon from '$lib/components/icons/TrashIcon.svelte';
import ModalTags from '$lib/components/tags/ModalTags.svelte'; import ModalTags from '$lib/components/tags/ModalTags.svelte';
import cards from '$lib/stores/cards';
import currentModalCard from '$lib/stores/currentModalCard'; import currentModalCard from '$lib/stores/currentModalCard';
import type Card from '$lib/types/Card'; import type Card from '$lib/types/Card';
import { cards } from '$lib/types/Card';
export let card: Card; export let card: Card;
@ -13,19 +13,11 @@
async function save(closeModal: boolean = true) { async function save(closeModal: boolean = true) {
if (card.title !== newTitle || card.content !== newContent) { if (card.title !== newTitle || card.content !== newContent) {
console.log('saving'); if (!(await card.update(newTitle, newContent))) return;
if (
await cards.edit({
...card,
title: newTitle,
content: newContent
})
) {
card.title = newTitle;
card.content = newContent;
}
} }
if (closeModal) currentModalCard.set(null); if (closeModal) currentModalCard.set(null);
cards.reload();
} }
</script> </script>
@ -37,7 +29,7 @@
<div class="header"> <div class="header">
<input class="title" bind:value={newTitle} on:blur={() => save(false)} /> <input class="title" bind:value={newTitle} on:blur={() => save(false)} />
<div class="buttons"> <div class="buttons">
<button on:click={() => cards.remove(card)}> <button on:click={() => card.delete()}>
<TrashIcon /> <TrashIcon />
</button> </button>
<button on:click={() => currentModalCard.set(null)}> <button on:click={() => currentModalCard.set(null)}>

View File

@ -1,12 +1,12 @@
<script lang="ts"> <script lang="ts">
import AddIcon from '$lib/components/icons/AddIcon.svelte'; import AddIcon from '$lib/components/icons/AddIcon.svelte';
import Menu from '$lib/components/menu/Menu.svelte'; import Menu from '$lib/components/menu/Menu.svelte';
import project_tags from '$lib/stores/projectTags';
import { toastAlert } from '$lib/utils/toasts';
import { tick } from 'svelte'; import { tick } from 'svelte';
import ModalTagTypes from './ModalTagTypes.svelte'; import ModalTagTypes from './ModalTagTypes.svelte';
import type Project from '$lib/types/Project';
import ProjectTag from '$lib/types/ProjectTag';
export let projectId: number; export let project: Project;
let isOpen = false; let isOpen = false;
@ -23,11 +23,10 @@
async function createTag() { async function createTag() {
if (title == '') return; if (title == '') return;
if (!projectId) { const res = await ProjectTag.create(project, title, typeId);
toastAlert('Failed to create tag', `ProjectId is ${projectId}`);
return; if (!res) return;
}
await project_tags.add(projectId, title, typeId);
isOpen = false; isOpen = false;
} }
</script> </script>

View File

@ -1,18 +1,16 @@
<script lang="ts"> <script lang="ts">
import type Card from '$lib/types/Card'; import type Card from '$lib/types/Card';
import type MeTag from '$lib/types/MeTag'; import type CardTag from '$lib/types/CardTag';
import type TagValue from '$lib/types/TagValue'; import type ProjectTag from '$lib/types/ProjectTag';
import ModalTagTitle from './ModalTagTitle.svelte'; import ModalTagTitle from './ModalTagTitle.svelte';
import ModalTagValue from './ModalTagValue.svelte'; import ModalTagValue from './ModalTagValue.svelte';
export let projectTag: MeTag; export let projectTag: ProjectTag;
export let tagValue: TagValue | undefined; export let cardTag: CardTag | undefined;
export let card: Card; export let card: Card;
</script> </script>
{#if projectTag}
<tr> <tr>
<ModalTagTitle {projectTag} /> <ModalTagTitle {projectTag} />
<ModalTagValue {projectTag} {tagValue} {card} /> <ModalTagValue {projectTag} {cardTag} {card} />
</tr> </tr>
{/if}

View File

@ -1,18 +1,18 @@
<script lang="ts"> <script lang="ts">
import Menu from '$lib/components/menu/Menu.svelte'; import Menu from '$lib/components/menu/Menu.svelte';
import project_tags from '$lib/stores/projectTags';
import type MeTag from '$lib/types/MeTag';
import { toastAlert } from '$lib/utils/toasts'; import { toastAlert } from '$lib/utils/toasts';
import { tick } from 'svelte'; import { tick } from 'svelte';
import ModalTagTypes from './ModalTagTypes.svelte'; import ModalTagTypes from './ModalTagTypes.svelte';
import type ProjectTag from '$lib/types/ProjectTag';
export let projectTag: MeTag; export let projectTag: ProjectTag;
let askConfirm: boolean = false; let askConfirm: boolean = false;
let titleInput: HTMLInputElement; let titleInput: HTMLInputElement;
let isMenuOpen: boolean = false; let isMenuOpen: boolean = false;
let lastTitle: string = projectTag.title; let newTitle: string = projectTag.title;
let newType: number = projectTag.type;
async function openMenu() { async function openMenu() {
isMenuOpen = !isMenuOpen; isMenuOpen = !isMenuOpen;
@ -21,16 +21,14 @@
} }
async function saveProjectTag() { async function saveProjectTag() {
if (projectTag.title === lastTitle) return; if (newTitle === projectTag.title) return;
if (projectTag.title === '') { if (newTitle === '') {
toastAlert('Tag title cannot be empty'); toastAlert('Tag title cannot be empty');
return; return;
} }
await project_tags.update(projectTag); return await projectTag.update(newTitle, newType);
lastTitle = projectTag.title;
} }
</script> </script>
@ -48,17 +46,11 @@
> >
{projectTag.title} {projectTag.title}
</div> </div>
<Menu <Menu bind:isOpen={isMenuOpen} onLeave={() => (askConfirm = false)}>
bind:isOpen={isMenuOpen}
onLeave={() => {
askConfirm = false;
projectTag.title = lastTitle;
}}
>
<div class="menu-items"> <div class="menu-items">
<input <input
bind:this={titleInput} bind:this={titleInput}
bind:value={projectTag.title} bind:value={newTitle}
on:blur={saveProjectTag} on:blur={saveProjectTag}
on:keydown={(e) => { on:keydown={(e) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
@ -67,11 +59,9 @@
}} }}
/> />
<ModalTagTypes <ModalTagTypes
type={projectTag.type} type={newType}
onChoice={async (id) => { onChoice={async (id) => {
projectTag.type = id; saveProjectTag();
await project_tags.update(projectTag);
}} }}
/> />
{#if askConfirm} {#if askConfirm}
@ -79,8 +69,8 @@
<span>Confirm?</span> <span>Confirm?</span>
<div> <div>
<button <button
on:click={() => { on:click={async () => {
project_tags.delete(projectTag.id); if (!(await projectTag.delete())) return;
isMenuOpen = false; isMenuOpen = false;
}}>✓</button }}>✓</button
> >

View File

@ -1,12 +1,12 @@
<script lang="ts"> <script lang="ts">
import type Card from '$lib/types/Card'; import type Card from '$lib/types/Card';
import type MeTag from '$lib/types/MeTag'; import type CardTag from '$lib/types/CardTag';
import type TagValue from '$lib/types/TagValue'; import type ProjectTag from '$lib/types/ProjectTag';
import { getTagTypeFromId } from '$lib/utils/tagTypes'; import { getTagTypeFromId } from '$lib/utils/tagTypes';
import SelectTags from './SelectTags.svelte'; import SelectTags from './SelectTags.svelte';
export let projectTag: MeTag; export let projectTag: ProjectTag;
export let tagValue: TagValue | undefined; export let cardTag: CardTag | undefined;
export let card: Card; export let card: Card;
let tagType = getTagTypeFromId(projectTag.type); let tagType = getTagTypeFromId(projectTag.type);
@ -15,7 +15,7 @@
{#if tagType} {#if tagType}
<td> <td>
{#if tagType?.hasOptions} {#if tagType?.hasOptions}
<SelectTags multiple={false} {projectTag} {card} {tagValue} /> <SelectTags multiple={false} {projectTag} {card} {cardTag} />
{:else if !tagType?.hasOptions} {:else if !tagType?.hasOptions}
<input /> <input />
{/if} {/if}

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import ProjectTags from '$lib/stores/projectTags';
import type Card from '$lib/types/Card'; import type Card from '$lib/types/Card';
import { projectTags } from '$lib/types/ProjectTag';
import ModalNewTag from './ModalNewTag.svelte'; import ModalNewTag from './ModalNewTag.svelte';
import ModalTag from './ModalTag.svelte'; import ModalTag from './ModalTag.svelte';
@ -8,10 +8,12 @@
</script> </script>
<table> <table>
{#if card.tags} {#each $projectTags as projectTag}
{#each Object.values($ProjectTags) as projectTag} <ModalTag
<ModalTag tagValue={card.tags.find((t) => t.tag_id === projectTag.id)} {projectTag} {card} /> cardTag={card.cardTags.find((t) => t.projectTag === projectTag)}
{projectTag}
{card}
/>
{/each} {/each}
{/if} <ModalNewTag project={card.project} />
<ModalNewTag projectId={card.project_id} />
</table> </table>

View File

@ -1,88 +1,42 @@
<script lang="ts"> <script lang="ts">
import Menu from '$lib/components/menu/Menu.svelte'; import Menu from '$lib/components/menu/Menu.svelte';
import cards from '$lib/stores/cards';
import project_tags from '$lib/stores/projectTags';
import type Card from '$lib/types/Card'; import type Card from '$lib/types/Card';
import type MeTag from '$lib/types/MeTag'; import { cards } from '$lib/types/Card';
import type TagValue from '$lib/types/TagValue'; import CardTag from '$lib/types/CardTag';
import type ProjectTag from '$lib/types/ProjectTag';
import type TagOption from '$lib/types/TagOption';
import api, { processError } from '$lib/utils/api'; import api, { processError } from '$lib/utils/api';
import status from '$lib/utils/status'; import status from '$lib/utils/status';
import { updatimport } from { updateCardTagApi }; import TrashIcon from '../icons/TrashIcon.svelte';
export const multiple: boolean = false; export const multiple: boolean = false;
export let card: Card; export let card: Card;
export let projectTag: MeTag; export let projectTag: ProjectTag;
export let tagValue: TagValue | undefined; export let cardTag: CardTag | undefined;
let lastTagValue = { ...tagValue };
let newOption: string = ''; let newOption: string = '';
$: tagOption = projectTag.options.find((o) => o.id === tagValue?.option_id);
let isOpen = false; let isOpen = false;
async function selectOption(option_id: number | null) { async function selectOption(option: TagOption | null) {
if (lastTagValue.option_id === option_id) { if (cardTag?.option === option) {
isOpen = false; isOpen = false;
return; return;
} }
if (tagValue) { if (cardTag) {
await updateCardTagApi(card.id, projectTag.id, option_id, tagValue.value); if (option) await cardTag.update(option, null);
else await cardTag.delete();
card.tags = card.tags.map((t) => {
if (t.tag_id === projectTag.id) {
t.option_id = option_id;
}
return t;
});
tagValue = { ...tagValue, option_id };
} else { } else {
const response = await api.post(`/v1/cards/${card.id}/tags/${projectTag.id}`, { if (option) await CardTag.create(card, projectTag, option, null);
option_id,
value: ''
});
if (response.status !== status.Created) {
processError(response, 'Failed to create tag');
return;
} }
tagValue = {
card_id: card.id,
tag_id: projectTag.id,
option_id,
value: ''
};
card.tags.push(tagValue);
}
lastTagValue = { ...tagValue };
isOpen = false; isOpen = false;
cards.reload(); cards.reload();
} }
async function deleteOption() { async function createOption() {
const response = await api.delete(`/v1/cards/${card.id}/tags/${projectTag.id}`);
if (response.status !== status.NoContent) {
processError(response, 'Failed to delete tag');
return;
}
tagValue = undefined;
card.tags = card.tags.filter((t) => t.tag_id !== projectTag.id);
cards.reload();
}
function createOption() {
if (!newOption) return; if (!newOption) return;
project_tags.addOption(projectTag.id, newOption); if (!(await projectTag.addOption(newOption))) return;
newOption = ''; newOption = '';
} }
</script> </script>
@ -99,10 +53,10 @@
}} }}
> >
<div class="tags"> <div class="tags">
{#if tagValue} {#if cardTag}
<span class="tag"> <span class="tag">
{tagOption?.value} {cardTag.value}
<button class="real" on:click={() => deleteOption()}>✗</button> <button class="real" on:click={() => selectOption(null)}>✗</button>
</span> </span>
{/if} {/if}
</div> </div>
@ -116,19 +70,19 @@
{#each projectTag.options as option} {#each projectTag.options as option}
<div <div
class="option" class="option"
on:click={() => selectOption(option.id)} on:click={() => selectOption(option)}
tabindex="0" tabindex="0"
role="button" role="button"
on:keydown={(e) => { on:keydown={(e) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
selectOption(option.id); selectOption(option);
} }
}} }}
> >
<span class="value">{option.value}</span> <span class="value">{option.value}</span>
<button <button
on:click|stopPropagation={() => { on:click|stopPropagation={() => {
project_tags.deleteOption(projectTag.id, option.id); projectTag.deleteOption(option);
}}><TrashIcon size={16} /></button }}><TrashIcon size={16} /></button
> >
</div> </div>

View File

@ -120,6 +120,37 @@ export default class Card {
return await cardTag.update(tagOption, value); return await cardTag.update(tagOption, value);
} }
async updateTitle(title: string): Promise<boolean> {
const res = await cardsApi.update(this.id, this.project.id, title, this.content);
if (!res) return false;
this._title = title;
return true;
}
async updateContent(content: string): Promise<boolean> {
const res = await cardsApi.update(this.id, this.project.id, this.title, content);
if (!res) return false;
this._content = content;
return true;
}
async update(title: string, content: string): Promise<boolean> {
const res = await cardsApi.update(this.id, this.project.id, title, content);
if (!res) return false;
this._title = title;
this._content = content;
return true;
}
static parse(json: any): Card | null; static parse(json: any): Card | null;
static parse(json: any, project: Project | null | undefined): Card | null; static parse(json: any, project: Project | null | undefined): Card | null;

View File

@ -3,9 +3,12 @@
import { get, writable } from 'svelte/store'; import { get, writable } from 'svelte/store';
import TagOption from './TagOption'; import TagOption from './TagOption';
import Project from './Project'; import Project from './Project';
import projectTagsApi from '$lib/api/projectTagsApi';
export const projectTags = writable([] as ProjectTag[]); export const projectTags = writable([] as ProjectTag[]);
export const ProjectTagTypes = {};
export default class ProjectTag { export default class ProjectTag {
private _id: number; private _id: number;
private _project: Project; private _project: Project;
@ -58,6 +61,46 @@ export default class ProjectTag {
return null; return null;
} }
static async create(project: Project, title: string, type: number): Promise<ProjectTag | null> {
const response = await projectTagsApi.create(project.id, title, type);
if (!response) return null;
const projectTag = new ProjectTag(0, project, title, type, []);
projectTags.update((projectTags) => [...projectTags, projectTag]);
return projectTag;
}
async delete(): Promise<boolean> {
return await projectTagsApi.delete(this.id);
}
async update(title: string, type: number): Promise<boolean> {
return await projectTagsApi.update(this.id, title, type);
}
async addOption(title: string): Promise<TagOption | null> {
const option = await TagOption.create(this, title);
if (!option) return null;
this._options = [...this._options, option];
return option;
}
async deleteOption(option: TagOption): Promise<boolean> {
const success = await option.delete();
if (!success) return false;
this._options = this._options.filter((o) => o.id !== option.id);
return true;
}
static parse(json: any): ProjectTag | null; static parse(json: any): ProjectTag | null;
static parse(json: any, project: Project | null | undefined): ProjectTag | null; static parse(json: any, project: Project | null | undefined): ProjectTag | null;

View File

@ -24,6 +24,18 @@ export default class TagOption {
return this._value; return this._value;
} }
static async create(projectTag: ProjectTag, value: string): Promise<TagOption | null> {
const id = await tagsOptions.create(projectTag.id, value);
if (!id) return null;
return new TagOption(id, projectTag, value);
}
async delete(): Promise<boolean> {
return await tagsOptions.delete(this._id, this.projectTag.id);
}
async setValue(value: string): Promise<boolean> { async setValue(value: string): Promise<boolean> {
const res = await tagsOptions.update(this._id, this.projectTag.id, value); const res = await tagsOptions.update(this._id, this.projectTag.id, value);