Reformat project frontend
This commit is contained in:
parent
10b11f10dd
commit
16996de532
|
@ -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: []
|
||||||
|
|
|
@ -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 />
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 |
|
@ -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 |
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
});
|
});
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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[];
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
import { writable } from 'svelte/store';
|
|
||||||
|
|
||||||
export default writable(-1);
|
|
|
@ -1,4 +0,0 @@
|
||||||
import { writable } from "svelte/store";
|
|
||||||
import type { View } from "./interfaces";
|
|
||||||
|
|
||||||
export default writable(null as View | null);
|
|
|
@ -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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
Loading…
Reference in New Issue