Reformat project frontend

This commit is contained in:
Brieuc Dubois 2023-12-31 17:50:04 +01:00
parent 10b11f10dd
commit 16996de532
13 changed files with 152 additions and 174 deletions

View File

@ -18,7 +18,7 @@ export async function newCardApi(projectId: number): Promise<Card> {
return { return {
id: id, id: id,
project_id: projectId, projectId: projectId,
title: 'Untitled', title: 'Untitled',
content: '', content: '',
tags: [] tags: []

View File

@ -1,35 +1,23 @@
<script lang="ts"> <script lang="ts">
import currentModalCard from '../stores/currentModalCard';
import type { Card } from '../stores/interfaces'; import type { Card } from '../stores/interfaces';
import projectTags from '../stores/projectTags'; import projectTags from '../stores/projectTags';
import { currentModalCard } from '../stores/smallStore';
import ModalCard from './modal_card.svelte'; import ModalCard from './modal_card.svelte';
export let card: Card; export let card: Card;
let showModal: boolean = $currentModalCard == card.id;
export let onDelete: () => void;
function editCard() {
showModal = true;
}
function cancelEdit() {
showModal = false;
}
function editCardHandler(event: KeyboardEvent) {
if (event.key === 'Enter') {
editCard();
}
}
</script> </script>
<div <div
class="card" class="card"
tabindex="0" tabindex="0"
draggable={true} draggable={true}
on:click={editCard} on:click={() => ($currentModalCard = card.id)}
role="button" role="button"
on:keydown={editCardHandler} on:keydown={(e) => {
if (e.key === 'Enter') {
$currentModalCard = card.id;
}
}}
> >
<div class="title">{card.title}</div> <div class="title">{card.title}</div>
{#if card.tags} {#if card.tags}
@ -49,4 +37,4 @@
{/if} {/if}
</div> </div>
<ModalCard bind:show={showModal} bind:card onCancel={cancelEdit} {onDelete} /> <ModalCard bind:card />

View File

@ -2,21 +2,15 @@
import type { Card, TagOption } from '../stores/interfaces'; import type { Card, TagOption } from '../stores/interfaces';
import CardC from './card.svelte'; import CardC from './card.svelte';
export let tag_id: number; export let title: string;
export let option: TagOption;
export let cards: Card[] = []; export let cards: Card[] = [];
export let deleteCard: (id: number) => void;
let columnCards = cards.filter(
(card) => card.tags.find((t) => t.tag_id === tag_id)?.option_id === option.id
);
</script> </script>
<div class="column"> <div class="column">
<h3>{option.value}</h3> <h3>{title}</h3>
<ul> <ul>
{#each columnCards as card} {#each cards as card}
<CardC {card} onDelete={async () => await deleteCard(card.id)} /> <CardC {card} />
{/each} {/each}
</ul> </ul>
</div> </div>

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="white" viewBox="0 0 24 24">
<path d="M0 0h24v24H0z" fill="none" />
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 12 12z"
/>
</svg>

After

Width:  |  Height:  |  Size: 260 B

View File

@ -0,0 +1,17 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="white"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M3 6h18"></path>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m2-2h10a2 2 0 0 1 2 2v2H5V6a2 2 0 0 1 2-2z"
></path>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>

After

Width:  |  Height:  |  Size: 417 B

View File

@ -3,22 +3,22 @@
import type { Card } from '../stores/interfaces'; import type { Card } 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';
import { cards, currentModalCard } from '../stores/smallStore';
import TrashIcon from './icons/trashIcon.svelte';
import CloseIcon from './icons/closeIcon.svelte';
export let show: boolean;
export let card: Card; export let card: Card;
export let onCancel: () => void;
export let onDelete: () => void;
let tempCard: Card = { ...card }; let tempCard: Card = { ...card };
async function save(closeModal: boolean = true) { async function save(closeModal: boolean = true) {
if ( if (
card.project_id != tempCard.project_id || card.projectId != tempCard.projectId ||
card.title !== tempCard.title || card.title !== tempCard.title ||
card.content !== tempCard.content card.content !== tempCard.content
) { ) {
const response = await api.put(`/v1/cards/${card.id}`, { const response = await api.put(`/v1/cards/${card.id}`, {
project_id: tempCard.project_id, project_id: tempCard.projectId,
title: tempCard.title, title: tempCard.title,
content: tempCard.content content: tempCard.content
}); });
@ -30,11 +30,11 @@
card = { ...tempCard }; card = { ...tempCard };
} }
if (closeModal) show = false; if (closeModal) currentModalCard.set(-1);
} }
</script> </script>
{#if show} {#if $currentModalCard == card.id}
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions --> <!-- svelte-ignore a11y-no-static-element-interactions -->
<div class="modal" on:click={() => save(true)}> <div class="modal" on:click={() => save(true)}>
@ -42,39 +42,11 @@
<div class="header"> <div class="header">
<input class="title" bind:value={tempCard.title} on:blur={() => save(false)} /> <input class="title" bind:value={tempCard.title} on:blur={() => save(false)} />
<div class="buttons"> <div class="buttons">
<button on:click={onDelete}> <button on:click={() => cards.remove(card)}>
<svg <TrashIcon />
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="white"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M3 6h18"></path>
<path
d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m2-2h10a2 2 0 0 1 2 2v2H5V6a2 2 0 0 1 2-2z"
></path>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>
</button> </button>
<button on:click={onCancel}> <button on:click={() => currentModalCard.set(-1)}>
<svg <CloseIcon />
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="white"
viewBox="0 0 24 24"
>
<path d="M0 0h24v24H0z" fill="none" />
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 12 12z"
/>
</svg>
</button> </button>
</div> </div>
</div> </div>

View File

@ -12,7 +12,7 @@
if (newTagName === '') return; if (newTagName === '') return;
const response = await api.post(`/v1/tags`, { const response = await api.post(`/v1/tags`, {
project_id: card.project_id, project_id: card.projectId,
title: newTagName, title: newTagName,
type: 0 type: 0
}); });

View File

@ -1,18 +1,16 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import CardC from './card.svelte';
import { type Project, type Card, parseCards, type View } from '../stores/interfaces'; import { type Project, type Card, parseCards, type View } from '../stores/interfaces';
import projectTags from '../stores/projectTags'; import projectTags from '../stores/projectTags';
import currentView from '../stores/currentView';
import { deleteCardApi, newCardApi } from '../api/cards'; import { deleteCardApi, newCardApi } from '../api/cards';
import { getProjectAPI, getProjectCardsAPI } from '../api/projects'; import { getProjectAPI, getProjectCardsAPI } from '../api/projects';
import Column from './column.svelte'; import Column from './column.svelte';
import currentModalCard from '../stores/currentModalCard'; import { cards, currentModalCard, currentView } from '../stores/smallStore';
export let projectId: number; export let projectId: number;
let project: Project; let project: Project;
let cards: Card[] = []; // let cards: Card[] = [];
let view: View | null = null; let view: View | null = null;
let columns: { id: number; title: string; cards: Card[] }[] = []; let columns: { id: number; title: string; cards: Card[] }[] = [];
@ -21,8 +19,9 @@
project = p; project = p;
}); });
getProjectCardsAPI(projectId).then((c) => { cards.init(projectId);
cards = parseCards(c);
cards.subscribe((c) => {
loadColumns(); loadColumns();
}); });
@ -36,21 +35,6 @@
}); });
}); });
function newCard() {
newCardApi(projectId).then((card) => {
cards = [...cards, card];
currentModalCard.set(card.id);
loadColumns();
});
}
function deleteCard(id: number) {
deleteCardApi(id).then(() => {
cards = cards.filter((card) => card.id !== id);
loadColumns();
});
}
function loadColumns() { function loadColumns() {
if (!view) return; if (!view) return;
let primary_tag_id = view.primary_tag_id; let primary_tag_id = view.primary_tag_id;
@ -59,18 +43,23 @@
return { return {
id: o.id, id: o.id,
title: o.value, title: o.value,
cards: cards.filter((c) => c.tags.map((t) => t.option_id).includes(o.id)) cards: $cards?.filter((c) => c.tags.map((t) => t.option_id).includes(o.id)) || []
}; };
}) || []; }) || [];
columns.push({ columns.push({
id: -1, id: -1,
title: 'No tag', title: 'No tag',
cards: cards.filter((c) => { cards:
const tag = c.tags.find((t) => t.tag_id === primary_tag_id); $cards?.filter((c) => {
return tag?.option_id == -1; const tag = c.tags.find((t) => t.tag_id === primary_tag_id);
}) return tag?.option_id == -1;
}) || []
}); });
} }
async function newCard() {
await cards.add(projectId);
}
</script> </script>
<svelte:head> <svelte:head>
@ -85,40 +74,26 @@
<h2>{project.title}</h2> <h2>{project.title}</h2>
<button on:click={newCard}>New card</button> <button on:click={newCard}>New card</button>
</header> </header>
{#if view && $projectTags[view.primary_tag_id]} {#if view && $projectTags[view.primary_tag_id] && $cards}
<div class="grid"> <div class="grid">
{#each $projectTags[view.primary_tag_id].options as option} {#each $projectTags[view.primary_tag_id].options as option}
<Column tag_id={view.primary_tag_id} {option} bind:cards deleteCard /> <Column
title={option.value}
cards={$cards.filter((c) => c.tags.map((t) => t.option_id).includes(option.id))}
/>
{/each} {/each}
<Column
title={`No ${$projectTags[view.primary_tag_id]?.title || 'tag'}`}
cards={$cards.filter((c) => c.tags.find((t) => t.tag_id)?.option_id == -1 || false)}
/>
</div> </div>
{:else}
<!-- <ul>
{#if cards}
{#each cards as card}
<CardC
{card}
showModal={modalID === card.id}
onDelete={async () => await deleteCard(card.id)}
/>
{/each}
{/if}
</ul> -->
{/if} {/if}
</div> </div>
{/if} {/if}
<style> <style>
#project .grid { .grid {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
} }
#project .column {
width: 200px;
margin: 0 10px;
}
#project .column h3 {
text-align: center;
}
</style> </style>

View File

@ -2,7 +2,7 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import api, { processError } from '../utils/api'; import api, { processError } from '../utils/api';
import type { View } from '../stores/interfaces'; import type { View } from '../stores/interfaces';
import currentView from '../stores/currentView'; import { currentView } from '../stores/smallStore';
export let projectID: number; export let projectID: number;
let views: View[]; let views: View[];

View File

@ -1,3 +0,0 @@
import { writable } from 'svelte/store';
export default writable(-1);

View File

@ -1,4 +0,0 @@
import { writable } from "svelte/store";
import type { View } from "./interfaces";
export default writable(null as View | null);

View File

@ -1,68 +1,68 @@
import { toastAlert } from "../utils/toasts"; import { toastAlert } from '../utils/toasts';
export interface Project { export interface Project {
id: number; id: number;
title: string; title: string;
} }
export interface Card { export interface Card {
id: number; id: number;
project_id: number; projectId: number;
title: string; title: string;
content: string; content: string;
tags: TagValue[]; tags: TagValue[];
} }
export interface TagValue { export interface TagValue {
card_id: number; card_id: number;
tag_id: number; tag_id: number;
option_id: number; option_id: number;
value: string; value: string;
} }
export interface MeTag { export interface MeTag {
id: number; id: number;
project_id: number; project_id: number;
title: string; title: string;
type: number; type: number;
options: TagOption[]; options: TagOption[];
} }
export interface TagOption { export interface TagOption {
id: number; id: number;
tag_id: number; tag_id: number;
value: string; value: string;
} }
export interface View { export interface View {
id: number; id: number;
project_id: number; project_id: number;
primary_tag_id: number; primary_tag_id: number;
secondary_tag_id: number; secondary_tag_id: number;
title: string; title: 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) {
if (cards == null) return [];
return cards.map((c: any) => parseCard(c));
} }
export function parseMeTag (t: any) { export function parseCards(cards: any) {
let tag: MeTag = t; if (cards == null) return [];
if (tag.options == null) tag.options = []; return cards.map((c: any) => parseCard(c));
return tag;
} }
export function parseMeTags (tags: any) { export function parseMeTag(t: any) {
if (tags == null) return {}; let tag: MeTag = t;
return tags.map(parseMeTag).reduce((acc: any, tag: MeTag) => { if (tag.options == null) tag.options = [];
acc[tag.id] = tag; return tag;
return acc; }
});
export function parseMeTags(tags: any) {
if (tags == null) return {};
return tags.map(parseMeTag).reduce((acc: any, tag: MeTag) => {
acc[tag.id] = tag;
return acc;
});
} }

View File

@ -0,0 +1,33 @@
import { writable } from 'svelte/store';
import { parseCards, type Card, type View } from './interfaces';
import { deleteCardApi, newCardApi } from '../api/cards';
import { getProjectCardsAPI } from '../api/projects';
export const currentView = writable(null as View | null);
export const currentModalCard = writable(-1);
export const cards = (() => {
const { subscribe, set, update } = writable([] as Card[]);
return {
subscribe,
init: async (projectId: number) => {
getProjectCardsAPI(projectId).then((c) => {
set(parseCards(c));
});
},
add: async (projectId: number) => {
await newCardApi(projectId).then((card) => {
currentModalCard.set(card.id);
update((cards) => [...cards, card]);
});
},
remove: async (card: Card) => {
await deleteCardApi(card.id).then(() => {
update((cards) => cards.filter((c) => c.id !== card.id));
currentModalCard.set(-1);
});
}
};
})();