Modal card
This commit is contained in:
parent
e38385cab5
commit
82ba28adeb
|
@ -30,7 +30,7 @@ func GetAllCardsOf(projectID int) ([]types.FullCard, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
tags, err := GetAllTagsOfCard(c.ID)
|
||||
tags, err := GetAllTagsOfCard(c.ID, projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ func GetCard(id int) (*types.FullCard, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
tags, err := GetAllTagsOfCard(id)
|
||||
tags, err := GetAllTagsOfCard(id, c.ProjectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -9,12 +9,20 @@ func AddTagToCard(ct types.CardTag) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func GetAllTagsOfCard(cardID int) ([]types.FullCardTag, error) {
|
||||
rows, err := db.Query(`SELECT ct.card_id, ct.tag_id, t.title, ct.value
|
||||
FROM cardtags ct
|
||||
JOIN tags t ON ct.tag_id = t.id
|
||||
WHERE ct.card_id = ?;
|
||||
`, cardID)
|
||||
func GetAllTagsOfCard(cardID int, projectID int) ([]types.FullCardTag, error) {
|
||||
if projectID < 0 {
|
||||
card, err := GetCard(cardID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
projectID = card.ProjectID
|
||||
}
|
||||
|
||||
rows, err := db.Query(`SELECT t.id, t.title, 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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -22,8 +30,8 @@ func GetAllTagsOfCard(cardID int) ([]types.FullCardTag, error) {
|
|||
|
||||
var cardtags []types.FullCardTag
|
||||
for rows.Next() {
|
||||
var ct types.FullCardTag
|
||||
if err := rows.Scan(&ct.CardID, &ct.TagID, &ct.TagTitle, &ct.Value); err != nil {
|
||||
ct := types.FullCardTag{CardID: cardID}
|
||||
if err := rows.Scan(&ct.TagID, &ct.TagTitle, &ct.Value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cardtags = append(cardtags, ct)
|
||||
|
|
|
@ -26,12 +26,12 @@ func CreateCard(c *fiber.Ctx) error {
|
|||
func GetAllCardsOf(c *fiber.Ctx) error {
|
||||
projectID, err := strconv.Atoi(c.Params("project_id"))
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"status": "error", "error": "Invalid project_id"})
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"status": "error", "error": "Invalid project_id", "trace": fmt.Sprint(err)})
|
||||
}
|
||||
|
||||
projects, err := db.GetAllCardsOf(projectID)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Cannot retrieve cards"})
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"status": "error", "error": "Cannot retrieve cards", "trace": fmt.Sprint(err)})
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{"status": "ok", "data": projects})
|
||||
|
|
|
@ -29,7 +29,7 @@ func GetAllTagsOfCard(c *fiber.Ctx) error {
|
|||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"status": "error", "error": "Invalid card_id"})
|
||||
}
|
||||
|
||||
cardtags, err := db.GetAllTagsOfCard(cardID)
|
||||
cardtags, err := db.GetAllTagsOfCard(cardID, -1)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"status": "error", "error": "Cannot retrieve tags of card", "stack": fmt.Sprint(err)})
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ func UpdateTagOfCard(c *fiber.Ctx) error {
|
|||
|
||||
tag_id, err := strconv.Atoi(c.Params("tag_id"))
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid tag ID"})
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"status": "error", "error": "Invalid tag ID", "stack": fmt.Sprint(err)})
|
||||
}
|
||||
|
||||
cardtag := types.CardTag{CardID: card_id, TagID: tag_id}
|
||||
|
|
|
@ -58,7 +58,7 @@ func main() {
|
|||
app.Get("/api/cardtags/:card_id", handlers.GetAllTagsOfCard)
|
||||
app.Delete("/api/cardtag/:card_id/:tag_id", handlers.DeleteTagOfCard)
|
||||
app.Delete("/api/cardtags/:card_id", handlers.DeleteTagsOfCard)
|
||||
app.Put("/api/cardtag/:card_id/:tag_id", handlers.UpdateTag)
|
||||
app.Put("/api/cardtag/:card_id/:tag_id", handlers.UpdateTagOfCard)
|
||||
|
||||
log.Fatal(app.Listen(fmt.Sprintf(":%v", port)))
|
||||
}
|
||||
|
|
|
@ -1,41 +1,52 @@
|
|||
<script lang="ts">
|
||||
import axios from 'axios';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
const backend = 'http://127.0.0.1:3000';
|
||||
|
||||
interface Card {
|
||||
id: number;
|
||||
title: string;
|
||||
content: string;
|
||||
tags: Tag[];
|
||||
}
|
||||
|
||||
interface Tag {
|
||||
tag_id: number;
|
||||
tag_title: string;
|
||||
value: string;
|
||||
}
|
||||
import type { Card } from '../stores/interfaces';
|
||||
import ModalCard from './modal_card.svelte';
|
||||
import { backend } from '../stores/config';
|
||||
|
||||
export let card: Card = {
|
||||
id: 0,
|
||||
project_id: 0,
|
||||
title: 'No title',
|
||||
content: 'Nocontent',
|
||||
tags: []
|
||||
};
|
||||
|
||||
let showModal = false;
|
||||
|
||||
function editCard() {
|
||||
showModal = true;
|
||||
}
|
||||
|
||||
function cancelEdit() {
|
||||
showModal = false;
|
||||
}
|
||||
|
||||
function editCardHandler(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter') {
|
||||
editCard();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<link rel="stylesheet" type="text/css" href="/css/card.css" />
|
||||
</svelte:head>
|
||||
|
||||
<div class="card" draggable={true}>
|
||||
<div
|
||||
class="card"
|
||||
tabindex="0"
|
||||
draggable={true}
|
||||
on:click={editCard}
|
||||
role="button"
|
||||
on:keydown={editCardHandler}
|
||||
>
|
||||
<div class="title">{card.title}</div>
|
||||
{#if card.tags}
|
||||
<div class="tags">
|
||||
{#each card.tags as tag}
|
||||
<span class="tag" style="border: 1px solid #333">{tag.value}</span>
|
||||
{#if tag.value}
|
||||
<span class="tag" style="border: 1px solid #333">{tag.value}</span>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<ModalCard bind:show={showModal} {card} onCancel={cancelEdit} />
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
<script lang="ts">
|
||||
import axios from 'axios';
|
||||
import ModalTags from './modal_tags.svelte';
|
||||
import type { Card } from '../stores/interfaces';
|
||||
import { backend } from '../stores/config';
|
||||
|
||||
export let show: boolean;
|
||||
export let card: Card;
|
||||
export let onCancel: () => void;
|
||||
|
||||
let tempCard: Card = { ...card };
|
||||
|
||||
function save(closeModal: boolean = true) {
|
||||
if (card != tempCard) {
|
||||
axios.put(`${backend}/api/card/${card.id}`, {
|
||||
project_id: tempCard.project_id,
|
||||
title: tempCard.title,
|
||||
content: tempCard.content
|
||||
});
|
||||
|
||||
card = tempCard;
|
||||
}
|
||||
if (closeModal) show = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if show}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="modal" on:click={() => save(true)}>
|
||||
<div class="content" on:click|stopPropagation>
|
||||
<div class="header">
|
||||
<input class="title" bind:value={tempCard.title} on:blur={() => save(false)} />
|
||||
<div class="buttons">
|
||||
<button on:click={() => save(true)}>
|
||||
<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="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button on:click={onCancel}>
|
||||
<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>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tags">
|
||||
<ModalTags {tempCard} />
|
||||
</div>
|
||||
<div class="body">
|
||||
<textarea
|
||||
bind:value={tempCard.content}
|
||||
placeholder="Add a description"
|
||||
on:blur={() => save(false)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
|
@ -0,0 +1,65 @@
|
|||
<script lang="ts">
|
||||
import axios from 'axios';
|
||||
import { backend } from '../stores/config';
|
||||
import type { Tag } from '../stores/interfaces';
|
||||
|
||||
export let tag: Tag = {
|
||||
card_id: 0,
|
||||
tag_id: 0,
|
||||
tag_title: 'No title',
|
||||
value: ''
|
||||
};
|
||||
|
||||
let newValue: string = tag.value;
|
||||
|
||||
export let removeTag: (id: number) => void;
|
||||
|
||||
function saveTag() {
|
||||
if (tag.value === newValue) return;
|
||||
// DELETE
|
||||
if (tag.value !== '' && newValue === '') {
|
||||
axios.delete(`${backend}/api/cardtag/${tag.card_id}/${tag.tag_id}`);
|
||||
return;
|
||||
}
|
||||
// CREATE
|
||||
if (tag.value === '' && newValue !== '') {
|
||||
axios.post(`${backend}/api/cardtag`, {
|
||||
card_id: tag.card_id,
|
||||
tag_id: tag.tag_id,
|
||||
value: newValue
|
||||
});
|
||||
return;
|
||||
}
|
||||
// UPDATE
|
||||
axios.put(`${backend}/api/cardtag/${tag.card_id}/${tag.tag_id}`, {
|
||||
value: newValue
|
||||
});
|
||||
|
||||
tag.value = newValue;
|
||||
}
|
||||
</script>
|
||||
|
||||
<tr class="tag">
|
||||
<td class="tag-title">{tag.tag_title}</td>
|
||||
<td>
|
||||
<input bind:value={newValue} on:blur={saveTag} />
|
||||
</td>
|
||||
<td>
|
||||
<button on:click={() => removeTag(tag.tag_id)} class="remove-tag-button">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="white"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
|
@ -0,0 +1,74 @@
|
|||
<script lang="ts">
|
||||
import axios from 'axios';
|
||||
import ModalTag from './modal_tag.svelte';
|
||||
import { backend } from '../stores/config';
|
||||
import type { Card } from '../stores/interfaces';
|
||||
|
||||
export let tempCard: Card = {
|
||||
id: 0,
|
||||
project_id: 0,
|
||||
title: 'No title',
|
||||
content: 'Nocontent',
|
||||
tags: []
|
||||
};
|
||||
|
||||
let newTagName = '';
|
||||
|
||||
async function addTag() {
|
||||
if (newTagName === '') return;
|
||||
|
||||
const response = await axios.post(`${backend}/api/tag`, {
|
||||
project_id: tempCard.project_id,
|
||||
title: newTagName,
|
||||
type: 0
|
||||
});
|
||||
|
||||
const { id } = response.data.id;
|
||||
|
||||
tempCard.tags = [
|
||||
...tempCard.tags,
|
||||
{ card_id: tempCard.id, tag_id: id, tag_title: newTagName, value: '' }
|
||||
];
|
||||
newTagName = '';
|
||||
}
|
||||
|
||||
function addTagHandler(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter') {
|
||||
addTag();
|
||||
}
|
||||
}
|
||||
|
||||
function removeTag(id: number) {
|
||||
tempCard.tags = tempCard.tags.filter((tag) => tag.tag_id !== id);
|
||||
}
|
||||
</script>
|
||||
|
||||
<table>
|
||||
{#if tempCard.tags}
|
||||
{#each tempCard.tags as tag}
|
||||
<ModalTag {tag} {removeTag} />
|
||||
{/each}
|
||||
{/if}
|
||||
<tr class="tag">
|
||||
<td class="tag-title"
|
||||
><input placeholder="Add a new tag" on:keydown={addTagHandler} bind:value={newTagName} /></td
|
||||
>
|
||||
<td
|
||||
><button on:click={addTag}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="white"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<line x1="12" y1="5" x2="12" y2="19"></line>
|
||||
<line x1="5" y1="12" x2="19" y2="12"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
|
@ -1,30 +1,12 @@
|
|||
<script lang="ts">
|
||||
import axios from 'axios';
|
||||
import { onMount } from 'svelte';
|
||||
import Card from './card.svelte';
|
||||
import CardC from './card.svelte';
|
||||
import { backend } from '../stores/config';
|
||||
import type { Card, Project } from '../stores/interfaces';
|
||||
|
||||
export let projectId: number = 0;
|
||||
|
||||
const backend = 'http://127.0.0.1:3000';
|
||||
|
||||
interface Project {
|
||||
id: number | undefined;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface Tag {
|
||||
tag_id: number;
|
||||
tag_title: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface Card {
|
||||
id: number;
|
||||
title: string;
|
||||
content: string;
|
||||
tags: Tag[];
|
||||
}
|
||||
|
||||
let project: Project;
|
||||
let cards: Card[];
|
||||
|
||||
|
@ -45,6 +27,8 @@
|
|||
|
||||
<svelte:head>
|
||||
<link rel="stylesheet" type="text/css" href="/css/project.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/css/card.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/css/modalCard.css" />
|
||||
</svelte:head>
|
||||
|
||||
{#if project}
|
||||
|
@ -54,7 +38,7 @@
|
|||
<ul>
|
||||
{#if cards}
|
||||
{#each cards as card}
|
||||
<Card {card} />
|
||||
<CardC {card} />
|
||||
{/each}
|
||||
{/if}
|
||||
</ul>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export const backend = 'http://127.0.0.1:3000';
|
|
@ -0,0 +1,19 @@
|
|||
export interface Project {
|
||||
id: number | undefined;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface Card {
|
||||
id: number;
|
||||
project_id: number;
|
||||
title: string;
|
||||
content: string;
|
||||
tags: Tag[];
|
||||
}
|
||||
|
||||
export interface Tag {
|
||||
card_id: number;
|
||||
tag_id: number;
|
||||
tag_title: string;
|
||||
value: string;
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
.modal {
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid green;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal .content {
|
||||
background: #1e1e1e;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.modal input,
|
||||
.modal textarea {
|
||||
background: none;
|
||||
color: inherit;
|
||||
border: 1px solid #333;
|
||||
border-radius: 7px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.modal .title {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal .header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.modal .buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal button {
|
||||
margin-left: 5px;
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.modal button:hover {
|
||||
background-color: #333;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal .buttons button:first-child:hover {
|
||||
background-color: #343;
|
||||
}
|
||||
|
||||
.modal .buttons button:last-child:hover {
|
||||
background-color: #433;
|
||||
}
|
||||
|
||||
.modal .body {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.modal textarea {
|
||||
width: 100%;
|
||||
min-height: 200px;
|
||||
resize: vertical;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.modal td {
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
.modal td:first-child {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.modal td:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.modal td button {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
Loading…
Reference in New Issue