View
This commit is contained in:
parent
b796195270
commit
4ca04b8cc8
|
@ -55,6 +55,16 @@ func InitDB(driver string, connStr string) error {
|
||||||
value TEXT,
|
value TEXT,
|
||||||
FOREIGN KEY(tag_id) REFERENCES tags(id)
|
FOREIGN KEY(tag_id) REFERENCES tags(id)
|
||||||
);
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS views (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
project_id INTEGER,
|
||||||
|
primary_tag_id INTEGER,
|
||||||
|
secondary_tag_id INTEGER,
|
||||||
|
title TEXT,
|
||||||
|
FOREIGN KEY(project_id) REFERENCES projects(id),
|
||||||
|
FOREIGN KEY(primary_tag_id) REFERENCES tags(id),
|
||||||
|
FOREIGN KEY(secondary_tag_id) REFERENCES tags(id)
|
||||||
|
);
|
||||||
`)
|
`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
package db
|
||||||
|
|
||||||
|
import "git.bhasher.com/bhasher/focus/backend/types"
|
||||||
|
|
||||||
|
func CreateView(v types.View) (int, error) {
|
||||||
|
res, err := db.Exec("INSERT INTO views (project_id, primary_tag_id, secondary_tag_id, title) VALUES (?, ?, ?, ?)", v.ProjectID, v.PrimaryTagID, v.SecondaryTagID, v.Title)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := res.LastInsertId()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(id), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProjectViews(projectID int) ([]types.View, error) {
|
||||||
|
rows, err := db.Query("SELECT * FROM views WHERE project_id = ?", projectID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var views []types.View
|
||||||
|
for rows.Next() {
|
||||||
|
var v types.View
|
||||||
|
if err := rows.Scan(&v.ID, &v.ProjectID, &v.PrimaryTagID, &v.SecondaryTagID, &v.Title); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
views = append(views, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return views, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetView(id int) (*types.View, error) {
|
||||||
|
rows, err := db.Query("SELECT * FROM views WHERE id = ?", id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
if !rows.Next() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var v types.View
|
||||||
|
rows.Scan(&v.ID, &v.ProjectID, v.PrimaryTagID, v.SecondaryTagID, v.Title)
|
||||||
|
|
||||||
|
return &v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateView(v types.View) (int64, error) {
|
||||||
|
res, err := db.Exec("UPDATE views SET project_id = ?, primary_tag_id = ?, secondary_tag_id = ?, title = ? WHERE id = ?", v.ProjectID, v.PrimaryTagID, v.SecondaryTagID, v.Title, v.ID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.RowsAffected()
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteView(id int) (int64, error) {
|
||||||
|
res, err := db.Exec("DELETE FROM views WHERE id = ?", id)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.RowsAffected()
|
||||||
|
}
|
|
@ -23,10 +23,7 @@ func cardsRouter(router fiber.Router) error {
|
||||||
func CreateCard(c *fiber.Ctx) error {
|
func CreateCard(c *fiber.Ctx) error {
|
||||||
card := types.Card{}
|
card := types.Card{}
|
||||||
if err := c.BodyParser(&card); err != nil {
|
if err := c.BodyParser(&card); err != nil {
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Cannot parse request"})
|
||||||
"error": "Cannot parse request",
|
|
||||||
"trace": fmt.Sprint(err),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := db.CreateCard(card)
|
id, err := db.CreateCard(card)
|
||||||
|
@ -45,10 +42,7 @@ func CreateCard(c *fiber.Ctx) error {
|
||||||
func GetCard(c *fiber.Ctx) error {
|
func GetCard(c *fiber.Ctx) error {
|
||||||
id, err := strconv.Atoi(c.Params("id"))
|
id, err := strconv.Atoi(c.Params("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid card ID"})
|
||||||
"error": "Invalid card ID",
|
|
||||||
"trace": fmt.Sprint(err),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
card, err := db.GetCard(id)
|
card, err := db.GetCard(id)
|
||||||
|
@ -68,10 +62,7 @@ func GetCard(c *fiber.Ctx) error {
|
||||||
func DeleteCard(c *fiber.Ctx) error {
|
func DeleteCard(c *fiber.Ctx) error {
|
||||||
id, err := strconv.Atoi(c.Params("id"))
|
id, err := strconv.Atoi(c.Params("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid card ID"})
|
||||||
"error": "Invalid card ID",
|
|
||||||
"trace": fmt.Sprint(err),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
count, err := db.DeleteCard(id)
|
count, err := db.DeleteCard(id)
|
||||||
|
|
|
@ -10,6 +10,7 @@ func v1Router(router fiber.Router) error {
|
||||||
projectsRouter(router.Group("/projects"))
|
projectsRouter(router.Group("/projects"))
|
||||||
cardsRouter(router.Group("/cards"))
|
cardsRouter(router.Group("/cards"))
|
||||||
tagsRouter(router.Group("/tags"))
|
tagsRouter(router.Group("/tags"))
|
||||||
|
viewsRouter(router.Group("/views"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ func projectsRouter(router fiber.Router) error {
|
||||||
router.Delete("/:id", DeleteProject)
|
router.Delete("/:id", DeleteProject)
|
||||||
router.Get(":id/cards", GetProjectCards)
|
router.Get(":id/cards", GetProjectCards)
|
||||||
router.Get(":id/tags", GetProjectTags)
|
router.Get(":id/tags", GetProjectTags)
|
||||||
|
router.Get(":id/views", GetProjectViews)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,3 +176,32 @@ func GetProjectTags(c *fiber.Ctx) error {
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).JSON(tags)
|
return c.Status(fiber.StatusOK).JSON(tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetProjectViews(c *fiber.Ctx) error {
|
||||||
|
projectID, err := strconv.Atoi(c.Params("id"))
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid project ID"})
|
||||||
|
}
|
||||||
|
|
||||||
|
exists, err := db.ExistProject(projectID)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "Error finding project",
|
||||||
|
"trace": fmt.Sprint(err),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return c.SendStatus(fiber.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
views, err := db.GetProjectViews(projectID)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "Cannot retrieve views",
|
||||||
|
"trace": fmt.Sprint(err),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(views)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.bhasher.com/bhasher/focus/backend/db"
|
||||||
|
"git.bhasher.com/bhasher/focus/backend/types"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func viewsRouter(router fiber.Router) error {
|
||||||
|
router.Post("/", CreateView)
|
||||||
|
router.Get("/:id", GetView)
|
||||||
|
router.Put("/:id", UpdateView)
|
||||||
|
router.Delete("/:id", DeleteView)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateView(c *fiber.Ctx) error {
|
||||||
|
view := types.View{}
|
||||||
|
if err := c.BodyParser(&view); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"error": "Cannot parse request",
|
||||||
|
"trace": fmt.Sprint(err),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := db.CreateView(view)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "Cannot create view",
|
||||||
|
"trace": fmt.Sprint(err),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
|
||||||
|
"id": id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetView(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.Atoi(c.Params("id"))
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid view ID"})
|
||||||
|
}
|
||||||
|
|
||||||
|
view, err := db.GetView(id)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "Cannot retrieve view",
|
||||||
|
"trace": fmt.Sprint(err),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if view == nil {
|
||||||
|
return c.SendStatus(fiber.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(fiber.StatusOK).JSON(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateView(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.Atoi(c.Params("id"))
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid view ID"})
|
||||||
|
}
|
||||||
|
|
||||||
|
view := types.View{ID: id}
|
||||||
|
if err := c.BodyParser(&view); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Cannot parse request"})
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := db.UpdateView(view)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "Cannot update view",
|
||||||
|
"trace": fmt.Sprint(err),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if count == 0 {
|
||||||
|
return c.SendStatus(fiber.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.SendStatus(fiber.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteView(c *fiber.Ctx) error {
|
||||||
|
id, err := strconv.Atoi(c.Params("id"))
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid view ID"})
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := db.DeleteView(id)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "Cannot delete view",
|
||||||
|
"trace": fmt.Sprint(err),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if count == 0 {
|
||||||
|
return c.SendStatus(fiber.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.SendStatus(fiber.StatusNoContent)
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
type View struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
ProjectID int `json:"project_id"`
|
||||||
|
PrimaryTagID int `json:"primary_tag_id"`
|
||||||
|
SecondaryTagID int `json:"secondary_tag_id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { type Card, type MeTag } 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';
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ModalTags from './modal_tags.svelte';
|
import ModalTags from './modal_tags.svelte';
|
||||||
import type { Card, MeTag } 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';
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { TagValue, MeTag } from '../stores/interfaces';
|
import type { TagValue } from '../stores/interfaces';
|
||||||
import projectTags from '../stores/projectTags';
|
import projectTags from '../stores/projectTags';
|
||||||
import api, { processError } from '../utils/api';
|
import api, { processError } from '../utils/api';
|
||||||
import status from '../utils/status';
|
import status from '../utils/status';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ModalTag from './modal_tag.svelte';
|
import ModalTag from './modal_tag.svelte';
|
||||||
import status from '../utils/status';
|
import status from '../utils/status';
|
||||||
import type { Card, MeTag } from '../stores/interfaces';
|
import type { Card } from '../stores/interfaces';
|
||||||
import api, { processError } from '../utils/api';
|
import api, { processError } from '../utils/api';
|
||||||
|
|
||||||
export let card: Card;
|
export let card: Card;
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import CardC from './card.svelte';
|
import CardC from './card.svelte';
|
||||||
import {
|
import { type Project, type Card, parseCards, type View } from '../stores/interfaces';
|
||||||
type Project,
|
|
||||||
type Card,
|
|
||||||
parseCards,
|
|
||||||
type MeTag,
|
|
||||||
parseMeTags
|
|
||||||
} from '../stores/interfaces';
|
|
||||||
import status from '../utils/status';
|
import status from '../utils/status';
|
||||||
import api, { processError } from '../utils/api';
|
import api, { processError } from '../utils/api';
|
||||||
import projectTags from '../stores/projectTags';
|
import projectTags from '../stores/projectTags';
|
||||||
|
import currentView from '../stores/currentView';
|
||||||
|
import Card from './card.svelte';
|
||||||
|
|
||||||
export let projectId: number;
|
export let projectId: number;
|
||||||
|
|
||||||
|
@ -80,6 +76,31 @@
|
||||||
|
|
||||||
cards = cards.filter((card) => card.id !== cardID);
|
cards = cards.filter((card) => card.id !== cardID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let view: View | null = null;
|
||||||
|
let columns: { id: number; title: string; cards: Card[] }[] = [];
|
||||||
|
|
||||||
|
currentView.subscribe((v) => {
|
||||||
|
view = v;
|
||||||
|
if (!v) return;
|
||||||
|
let primary_tag_id = v.primary_tag_id;
|
||||||
|
columns = $projectTags[primary_tag_id].options.map((o) => {
|
||||||
|
return {
|
||||||
|
id: o.id,
|
||||||
|
title: o.value,
|
||||||
|
cards: cards.filter((c) => c.tags.map((t) => t.option_id).includes(o.id))
|
||||||
|
};
|
||||||
|
});
|
||||||
|
columns.push({
|
||||||
|
id: -1,
|
||||||
|
title: 'No tag',
|
||||||
|
cards: cards.filter((c) => {
|
||||||
|
const tag = c.tags.find((t) => t.tag_id === primary_tag_id);
|
||||||
|
|
||||||
|
return tag?.option_id == -1;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
@ -94,6 +115,24 @@
|
||||||
<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}
|
||||||
|
<div class="grid">
|
||||||
|
{#each columns as column}
|
||||||
|
<div class="column">
|
||||||
|
<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}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
<ul>
|
<ul>
|
||||||
{#if cards}
|
{#if cards}
|
||||||
{#each cards as card}
|
{#each cards as card}
|
||||||
|
@ -105,5 +144,22 @@
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
</ul>
|
</ul>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#project .grid {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
#project .column {
|
||||||
|
width: 200px;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#project .column h3 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,73 +1,22 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import api, { processError } from '../utils/api';
|
import api, { processError } from '../utils/api';
|
||||||
import type { Project } from '../stores/interfaces';
|
import type { View } from '../stores/interfaces';
|
||||||
|
import currentView from '../stores/currentView';
|
||||||
|
|
||||||
let newProject = false;
|
export let projectID: number;
|
||||||
let editProject: number | undefined = undefined;
|
let views: View[];
|
||||||
let projects: Project[];
|
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const response = await api.get(`/v1/projects`);
|
const response = await api.get(`/v1/projects/${projectID}/views`);
|
||||||
|
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
processError(response, 'Failed to fetch projects');
|
processError(response, 'Failed to fetch views');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
projects = response.data;
|
views = response.data;
|
||||||
});
|
});
|
||||||
|
|
||||||
async function createProject(project: Project) {
|
|
||||||
const response = await api.post(`/v1/projects`, project);
|
|
||||||
|
|
||||||
if (response.status !== 201) {
|
|
||||||
processError(response, 'Failed to create project');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
project.id = response.data.id;
|
|
||||||
|
|
||||||
projects = [...projects, project];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateProject(project: Project) {
|
|
||||||
const response = await api.put(`/v1/projects/${project.id}`, project);
|
|
||||||
|
|
||||||
if (response.status !== 204) {
|
|
||||||
processError(response, 'Failed to update project');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
projects = projects.map((p) => {
|
|
||||||
if (p.id === project.id) {
|
|
||||||
return project;
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleKeydown(event: KeyboardEvent, id: number | undefined = undefined) {
|
|
||||||
if (event.key === 'Enter' && event.target) {
|
|
||||||
if (id !== undefined) {
|
|
||||||
updateProject({
|
|
||||||
id: id,
|
|
||||||
title: (event.target as HTMLInputElement).value
|
|
||||||
});
|
|
||||||
editProject = undefined;
|
|
||||||
} else {
|
|
||||||
createNewProject((event.target as HTMLInputElement).value);
|
|
||||||
newProject = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createNewProject(value: string) {
|
|
||||||
createProject({
|
|
||||||
title: value,
|
|
||||||
id: undefined
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
@ -84,38 +33,18 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="boards">
|
<div class="boards">
|
||||||
<h2>Projects</h2>
|
<h2>Projects</h2>
|
||||||
{#if projects}
|
{#if views}
|
||||||
<ul>
|
<ul>
|
||||||
{#each projects as project}
|
{#each views as view}
|
||||||
<li>
|
<li>
|
||||||
{#if editProject === project.id && editProject !== undefined}
|
<span
|
||||||
<input
|
on:click={() => {
|
||||||
type="text"
|
currentView.set(view);
|
||||||
on:keydown={(e) => handleKeydown(e, project.id)}
|
}}>{view.title}</span
|
||||||
value={project.title}
|
>
|
||||||
class="edit-input"
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<a href="/{project.id}">{project.title}</a>
|
|
||||||
<img src="img/edit-icon.svg" alt="" on:click={() => (editProject = project.id)} />
|
|
||||||
{/if}
|
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
{#if newProject}
|
|
||||||
<li>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Enter project title"
|
|
||||||
on:keydown={handleKeydown}
|
|
||||||
style="padding: 8px; border: 1px solid #ccc; border-radius: 4px; width: 100%;"
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
{/if}
|
|
||||||
</ul>
|
</ul>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="bottom-links">
|
|
||||||
<span on:click={() => (newProject = true)}>New project</span>
|
|
||||||
<span>Settings</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,13 +3,16 @@
|
||||||
import Sidebar from '../../components/sidebar.svelte';
|
import Sidebar from '../../components/sidebar.svelte';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { SvelteToast } from '@zerodevx/svelte-toast';
|
import { SvelteToast } from '@zerodevx/svelte-toast';
|
||||||
|
import type { View } from '../../stores/interfaces';
|
||||||
|
|
||||||
let projectId: number = +$page.params.project;
|
let projectID: number = +$page.params.project;
|
||||||
|
|
||||||
|
let currentView: View;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div id="projectPage">
|
<div id="projectPage">
|
||||||
<Sidebar />
|
<Sidebar {projectID} />
|
||||||
<Project {projectId} />
|
<Project projectId={projectID} />
|
||||||
</div>
|
</div>
|
||||||
<SvelteToast />
|
<SvelteToast />
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { writable } from "svelte/store";
|
||||||
|
import type { View } from "./interfaces";
|
||||||
|
|
||||||
|
export default writable(null as View | null);
|
|
@ -34,6 +34,14 @@ export interface TagOption {
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface View {
|
||||||
|
id: number;
|
||||||
|
project_id: number;
|
||||||
|
primary_tag_id: number;
|
||||||
|
secondary_tag_id: number;
|
||||||
|
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 = [];
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
border: 1px solid #555;
|
border: 1px solid #555;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
width: 200px;
|
/* width: 200px; */
|
||||||
font-family: "Open Sans", sans-serif;
|
font-family: "Open Sans", sans-serif;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue