Reformat cards

This commit is contained in:
Brieuc Dubois 2023-12-31 15:34:00 +01:00
parent 4ca04b8cc8
commit 10b11f10dd
8 changed files with 153 additions and 92 deletions

View File

@ -4,5 +4,10 @@
"trailingComma": "none", "trailingComma": "none",
"printWidth": 100, "printWidth": 100,
"plugins": ["prettier-plugin-svelte"], "plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] "overrides": [
{ "files": "*.svelte", "options": { "parser": "svelte" } },
{ "files": "*.ts", "options": { "parser": "typescript" } },
{ "files": "*.js", "options": { "parser": "babel" } },
{ "files": "*.css", "options": { "parser": "css" } }
]
} }

35
frontend/src/api/cards.ts Normal file
View File

@ -0,0 +1,35 @@
import type { Card } from '../stores/interfaces';
import api, { processError } from '../utils/api';
import status from '../utils/status';
export async function newCardApi(projectId: number): Promise<Card> {
const response = await api.post(`/v1/cards`, {
project_id: projectId,
title: 'Untitled',
content: ''
});
if (response.status !== status.Created) {
processError(response, 'Failed to create card');
return Promise.reject();
}
const id: number = response.data.id;
return {
id: id,
project_id: projectId,
title: 'Untitled',
content: '',
tags: []
};
}
export async function deleteCardApi(cardID: number): Promise<void> {
const response = await api.delete(`/v2/cards/${cardID}`);
if (response.status !== status.NoContent) {
processError(response, 'Failed to delete card');
return Promise.reject();
}
}

View File

@ -0,0 +1,25 @@
import { parseCards, type Card, type Project } from '../stores/interfaces';
import api, { processError } from '../utils/api';
import status from '../utils/status';
export async function getProjectAPI(projectId: number): Promise<Project> {
const response = await api.get(`/v1/projects/${projectId}`);
if (response.status !== status.OK) {
processError(response, 'Failed to fetch project');
return Promise.reject();
}
return response.data;
}
export async function getProjectCardsAPI(projectId: number): Promise<Card[]> {
const response = await api.get(`/v1/projects/${projectId}/cards`);
if (response.status !== status.OK) {
processError(response, 'Failed to fetch cards');
return Promise.reject();
}
return parseCards(response.data);
}

View File

@ -1,10 +1,11 @@
<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 ModalCard from './modal_card.svelte'; import ModalCard from './modal_card.svelte';
export let card: Card; export let card: Card;
export let showModal: boolean; let showModal: boolean = $currentModalCard == card.id;
export let onDelete: () => void; export let onDelete: () => void;
function editCard() { function editCard() {

View File

@ -0,0 +1,33 @@
<script lang="ts">
import type { Card, TagOption } from '../stores/interfaces';
import CardC from './card.svelte';
export let tag_id: number;
export let option: TagOption;
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>
<div class="column">
<h3>{option.value}</h3>
<ul>
{#each columnCards as card}
<CardC {card} onDelete={async () => await deleteCard(card.id)} />
{/each}
</ul>
</div>
<style>
h3 {
text-align: center;
}
.column {
width: 200px;
margin: 0 10px;
}
</style>

View File

@ -2,105 +2,75 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import CardC from './card.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 status from '../utils/status';
import api, { processError } from '../utils/api';
import projectTags from '../stores/projectTags'; import projectTags from '../stores/projectTags';
import currentView from '../stores/currentView'; import currentView from '../stores/currentView';
import Card from './card.svelte'; import { deleteCardApi, newCardApi } from '../api/cards';
import { getProjectAPI, getProjectCardsAPI } from '../api/projects';
import Column from './column.svelte';
import currentModalCard from '../stores/currentModalCard';
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 columns: { id: number; title: string; cards: Card[] }[] = [];
onMount(async () => { onMount(async () => {
let response = await api.get(`/v1/projects/${projectId}`); getProjectAPI(projectId).then((p) => {
project = p;
});
if (response.status !== status.OK) { getProjectCardsAPI(projectId).then((c) => {
processError(response, 'Failed to fetch project'); cards = parseCards(c);
return; loadColumns();
} });
project = response.data;
response = await api.get(`/v1/projects/${projectId}/cards`);
if (response.status === status.OK) {
cards = parseCards(response.data);
} else {
cards = [];
processError(response, 'Failed to fetch cards');
return;
}
if (!(await projectTags.init(projectId))) { if (!(await projectTags.init(projectId))) {
return; return;
} }
});
let modalID = -1;
async function newCard() {
const response = await api.post(`/v1/cards`, {
project_id: projectId,
title: 'Untitled',
content: ''
});
if (response.status !== status.Created) {
processError(response, 'Failed to create card');
return;
}
const id: number = response.data.id;
let card: Card = {
id: id,
project_id: projectId,
title: 'Untitled',
content: '',
tags: []
};
cards = [...cards, card];
modalID = id;
}
async function deleteCard(cardID: number) {
const response = await api.delete(`/v2/cards/${cardID}`);
if (response.status !== status.NoContent) {
processError(response, 'Failed to delete card');
return;
}
cards = cards.filter((card) => card.id !== cardID);
}
let view: View | null = null;
let columns: { id: number; title: string; cards: Card[] }[] = [];
currentView.subscribe((v) => { currentView.subscribe((v) => {
view = v; view = v;
if (!v) return; loadColumns();
let primary_tag_id = v.primary_tag_id; });
columns = $projectTags[primary_tag_id].options.map((o) => { });
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() {
if (!view) return;
let primary_tag_id = view.primary_tag_id;
columns =
$projectTags[primary_tag_id]?.options.map((o) => {
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: cards.filter((c) => {
const tag = c.tags.find((t) => t.tag_id === primary_tag_id); const tag = c.tags.find((t) => t.tag_id === primary_tag_id);
return tag?.option_id == -1; return tag?.option_id == -1;
}) })
}); });
}); }
</script> </script>
<svelte:head> <svelte:head>
@ -115,25 +85,14 @@
<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} {#if view && $projectTags[view.primary_tag_id]}
<div class="grid"> <div class="grid">
{#each columns as column} {#each $projectTags[view.primary_tag_id].options as option}
<div class="column"> <Column tag_id={view.primary_tag_id} {option} bind:cards deleteCard />
<h3>{column.title}</h3>
<ul>
{#each column.cards as card}
<CardC
{card}
showModal={modalID === card.id}
onDelete={async () => await deleteCard(card.id)}
/>
{/each}
</ul>
</div>
{/each} {/each}
</div> </div>
{:else} {:else}
<ul> <!-- <ul>
{#if cards} {#if cards}
{#each cards as card} {#each cards as card}
<CardC <CardC
@ -143,7 +102,7 @@
/> />
{/each} {/each}
{/if} {/if}
</ul> </ul> -->
{/if} {/if}
</div> </div>
{/if} {/if}

View File

@ -16,12 +16,12 @@
} }
views = response.data; views = response.data;
if (views.length > 0) currentView.set(views[0]);
}); });
</script> </script>
<svelte:head> <link rel="stylesheet" type="text/css" href="/css/sidebar.css" />
<link rel="stylesheet" type="text/css" href="/css/sidebar.css" />
</svelte:head>
<div id="sidebar" class="sidebar"> <div id="sidebar" class="sidebar">
<div class="logo"> <div class="logo">

View File

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