Replace -1 by nullable values
This commit is contained in:
parent
e2e87ce9ec
commit
98e54f94a6
|
@ -6,6 +6,8 @@ import (
|
||||||
|
|
||||||
var db *sql.DB
|
var db *sql.DB
|
||||||
|
|
||||||
|
const DB_VERSION = 1
|
||||||
|
|
||||||
func InitDB(driver string, connStr string) error {
|
func InitDB(driver string, connStr string) error {
|
||||||
var err error
|
var err error
|
||||||
db, err = sql.Open(driver, connStr)
|
db, err = sql.Open(driver, connStr)
|
||||||
|
@ -14,16 +16,13 @@ func InitDB(driver string, connStr string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = db.Exec(`
|
_, err = db.Exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS schema_version (
|
||||||
|
version INTEGER PRIMARY KEY,
|
||||||
|
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
CREATE TABLE IF NOT EXISTS projects (
|
CREATE TABLE IF NOT EXISTS projects (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
title TEXT
|
title TEXT
|
||||||
);
|
|
||||||
CREATE TABLE IF NOT EXISTS lists (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
project_id INTEGER,
|
|
||||||
title TEXT,
|
|
||||||
color TEXT,
|
|
||||||
FOREIGN KEY(project_id) REFERENCES projects(id)
|
|
||||||
);
|
);
|
||||||
CREATE TABLE IF NOT EXISTS cards (
|
CREATE TABLE IF NOT EXISTS cards (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
@ -61,13 +60,47 @@ func InitDB(driver string, connStr string) error {
|
||||||
primary_tag_id INTEGER,
|
primary_tag_id INTEGER,
|
||||||
secondary_tag_id INTEGER,
|
secondary_tag_id INTEGER,
|
||||||
title TEXT,
|
title TEXT,
|
||||||
|
sort_tag_id INTEGER,
|
||||||
|
sort_direction INTEGER,
|
||||||
FOREIGN KEY(project_id) REFERENCES projects(id),
|
FOREIGN KEY(project_id) REFERENCES projects(id),
|
||||||
FOREIGN KEY(primary_tag_id) REFERENCES tags(id),
|
FOREIGN KEY(primary_tag_id) REFERENCES tags(id),
|
||||||
FOREIGN KEY(secondary_tag_id) REFERENCES tags(id)
|
FOREIGN KEY(secondary_tag_id) REFERENCES tags(id)
|
||||||
);
|
);
|
||||||
`)
|
|
||||||
|
INSERT INTO schema_version (version)
|
||||||
|
SELECT ? WHERE NOT EXISTS (SELECT 1 FROM schema_version);
|
||||||
|
`, DB_VERSION)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return updateDB()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateDB() error {
|
||||||
|
|
||||||
|
var currentVersion int
|
||||||
|
|
||||||
|
err := db.QueryRow("SELECT MAX(version) FROM schema_version").Scan(¤tVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentVersion < 2 {
|
||||||
|
_, err := db.Exec(`
|
||||||
|
ALTER TABLE views
|
||||||
|
ADD COLUMN sort_tag_id INTEGER;
|
||||||
|
ALTER TABLE views
|
||||||
|
ADD COLUMN sort_direction INTEGER;
|
||||||
|
|
||||||
|
INSERT INTO schema_version (version)
|
||||||
|
SELECT ? WHERE NOT EXISTS (SELECT 1 FROM schema_version WHERE version = ?);
|
||||||
|
`, 2, 2)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package db
|
||||||
import "git.bhasher.com/bhasher/focus/backend/types"
|
import "git.bhasher.com/bhasher/focus/backend/types"
|
||||||
|
|
||||||
func CreateView(v types.View) (int, error) {
|
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)
|
res, err := db.Exec("INSERT INTO views (project_id, primary_tag_id, secondary_tag_id, title, sort_tag_id, sort_direction) VALUES (?, ?, ?, ?, ?, ?)", v.ProjectID, v.PrimaryTagID, v.SecondaryTagID, v.Title, v.SortTagID, v.SortDirection)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ func GetProjectViews(projectID int) ([]types.View, error) {
|
||||||
var views []types.View
|
var views []types.View
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var v types.View
|
var v types.View
|
||||||
if err := rows.Scan(&v.ID, &v.ProjectID, &v.PrimaryTagID, &v.SecondaryTagID, &v.Title); err != nil {
|
if err := rows.Scan(&v.ID, &v.ProjectID, &v.PrimaryTagID, &v.SecondaryTagID, &v.Title, &v.SortTagID, &v.SortDirection); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,13 +52,13 @@ func GetView(id int) (*types.View, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var v types.View
|
var v types.View
|
||||||
rows.Scan(&v.ID, &v.ProjectID, v.PrimaryTagID, v.SecondaryTagID, v.Title)
|
rows.Scan(&v.ID, &v.ProjectID, v.PrimaryTagID, v.SecondaryTagID, v.Title, v.SortTagID, v.SortDirection)
|
||||||
|
|
||||||
return &v, nil
|
return &v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateView(v types.View) (int64, error) {
|
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)
|
res, err := db.Exec("UPDATE views SET project_id = ?, primary_tag_id = ?, secondary_tag_id = ?, title = ?, sort_tag_id = ?, sort_direction = ? WHERE id = ?", v.ProjectID, v.PrimaryTagID, v.SecondaryTagID, v.Title, v.SortTagID, v.SortDirection, v.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@ package types
|
||||||
type CardTag struct {
|
type CardTag struct {
|
||||||
CardID int `json:"card_id"`
|
CardID int `json:"card_id"`
|
||||||
TagID int `json:"tag_id"`
|
TagID int `json:"tag_id"`
|
||||||
OptionID int `json:"option_id"`
|
OptionID *int `json:"option_id"`
|
||||||
Value string `json:"value"`
|
Value *string `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FullCardTag struct {
|
type FullCardTag struct {
|
||||||
|
@ -12,6 +12,6 @@ type FullCardTag struct {
|
||||||
TagID int `json:"tag_id"`
|
TagID int `json:"tag_id"`
|
||||||
TagTitle string `json:"tag_title"`
|
TagTitle string `json:"tag_title"`
|
||||||
TagType int `json:"tag_type"`
|
TagType int `json:"tag_type"`
|
||||||
OptionID int `json:"option_id"`
|
OptionID *int `json:"option_id"`
|
||||||
Value string `json:"value"`
|
Value *string `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
type List struct {
|
|
||||||
ID int `json:"id"`
|
|
||||||
ProjectID int `json:"project_id"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Color string `json:"color"`
|
|
||||||
}
|
|
|
@ -3,7 +3,9 @@ package types
|
||||||
type View struct {
|
type View struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
ProjectID int `json:"project_id"`
|
ProjectID int `json:"project_id"`
|
||||||
PrimaryTagID int `json:"primary_tag_id"`
|
|
||||||
SecondaryTagID int `json:"secondary_tag_id"`
|
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
|
PrimaryTagID *int `json:"primary_tag_id"`
|
||||||
|
SecondaryTagID *int `json:"secondary_tag_id"`
|
||||||
|
SortTagID *int `json:"sort_tag_id"`
|
||||||
|
SortDirection *string `json:"sort_direction"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.28.1",
|
"@playwright/test": "^1.28.1",
|
||||||
"@sveltejs/adapter-auto": "^3.0.0",
|
"@sveltejs/adapter-auto": "^3.0.0",
|
||||||
"@sveltejs/adapter-static": "^3.0.1",
|
|
||||||
"@sveltejs/kit": "^2.0.0",
|
"@sveltejs/kit": "^2.0.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||||
"@types/eslint": "8.56.0",
|
"@types/eslint": "8.56.0",
|
||||||
|
@ -836,15 +835,6 @@
|
||||||
"@sveltejs/kit": "^2.0.0"
|
"@sveltejs/kit": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sveltejs/adapter-static": {
|
|
||||||
"version": "3.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.1.tgz",
|
|
||||||
"integrity": "sha512-6lMvf7xYEJ+oGeR5L8DFJJrowkefTK6ZgA4JiMqoClMkKq0s6yvsd3FZfCFvX1fQ0tpCD7fkuRVHsnUVgsHyNg==",
|
|
||||||
"dev": true,
|
|
||||||
"peerDependencies": {
|
|
||||||
"@sveltejs/kit": "^2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@sveltejs/kit": {
|
"node_modules/@sveltejs/kit": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.0.6.tgz",
|
||||||
|
|
|
@ -60,8 +60,8 @@ export async function deleteCardApi(cardID: number): Promise<void> {
|
||||||
export async function createCardTagApi(
|
export async function createCardTagApi(
|
||||||
cardId: number,
|
cardId: number,
|
||||||
tagId: number,
|
tagId: number,
|
||||||
optionId: number,
|
optionId: number | null,
|
||||||
value: string
|
value: string | null
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const response = await api.post(`/v1/cards/${cardId}/tags/${tagId}`, {
|
const response = await api.post(`/v1/cards/${cardId}/tags/${tagId}`, {
|
||||||
option_id: optionId,
|
option_id: optionId,
|
||||||
|
@ -76,15 +76,24 @@ export async function createCardTagApi(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteCardTagApi(cardID: number, tagID: number): Promise<void> {
|
||||||
|
const response = await api.delete(`/v1/cards/${cardID}/tags/${tagID}`);
|
||||||
|
|
||||||
|
if (response.status !== status.NoContent) {
|
||||||
|
processError(response, 'Failed to delete tag');
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function updateCardTagApi(
|
export async function updateCardTagApi(
|
||||||
cardID: number,
|
cardID: number,
|
||||||
tagID: number,
|
tagID: number,
|
||||||
option_id: number,
|
option_id: number | null,
|
||||||
value: string
|
value: string | null
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const response = await api.put(`/v1/cards/${cardID}/tags/${tagID}`, {
|
const response = await api.put(`/v1/cards/${cardID}/tags/${tagID}`, {
|
||||||
option_id: option_id,
|
option_id: option_id == -1 ? null : option_id,
|
||||||
value: value
|
value: value == '' ? null : value
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status !== status.NoContent) {
|
if (response.status !== status.NoContent) {
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
{#if card.tags}
|
{#if card.tags}
|
||||||
<div class="tags">
|
<div class="tags">
|
||||||
{#each card.tags as tag}
|
{#each card.tags as tag}
|
||||||
{#if tag.option_id && tag.option_id !== -1}
|
{#if tag.option_id}
|
||||||
{#if $projectTags[tag.tag_id]}
|
{#if $projectTags[tag.tag_id]}
|
||||||
<span class="tag" style="border: 1px solid #333"
|
<span class="tag" style="border: 1px solid #333"
|
||||||
>{$projectTags[tag.tag_id]?.options.find((o) => o.id == tag.option_id)?.value}</span
|
>{$projectTags[tag.tag_id]?.options.find((o) => o.id == tag.option_id)?.value}</span
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
card.content = newContent;
|
card.content = newContent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (closeModal) currentModalCard.set(-1);
|
if (closeModal) currentModalCard.set(null);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
<button on:click={() => cards.remove(card)}>
|
<button on:click={() => cards.remove(card)}>
|
||||||
<TrashIcon />
|
<TrashIcon />
|
||||||
</button>
|
</button>
|
||||||
<button on:click={() => currentModalCard.set(-1)}>
|
<button on:click={() => currentModalCard.set(null)}>
|
||||||
<CloseIcon />
|
<CloseIcon />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { updateCardTagApi } from '../../../../api/cards';
|
||||||
import type { Card, MeTag, TagOption, TagValue } from '../../../../stores/interfaces';
|
import type { Card, MeTag, TagOption, TagValue } from '../../../../stores/interfaces';
|
||||||
import projectTags from '../../../../stores/projectTags';
|
import projectTags from '../../../../stores/projectTags';
|
||||||
import { cards } from '../../../../stores/smallStore';
|
import { cards } from '../../../../stores/smallStore';
|
||||||
|
@ -7,7 +8,7 @@
|
||||||
import TrashIcon from '../../../icons/trashIcon.svelte';
|
import TrashIcon from '../../../icons/trashIcon.svelte';
|
||||||
import Menu from '../../../tuils/menu.svelte';
|
import Menu from '../../../tuils/menu.svelte';
|
||||||
|
|
||||||
export let multiple: boolean = false;
|
export const multiple: boolean = false;
|
||||||
export let card: Card;
|
export let card: Card;
|
||||||
export let projectTag: MeTag;
|
export let projectTag: MeTag;
|
||||||
export let tagValue: TagValue | undefined;
|
export let tagValue: TagValue | undefined;
|
||||||
|
@ -19,21 +20,14 @@
|
||||||
|
|
||||||
let isOpen = false;
|
let isOpen = false;
|
||||||
|
|
||||||
async function selectOption(option_id: number) {
|
async function selectOption(option_id: number | null) {
|
||||||
if (lastTagValue.option_id === option_id) {
|
if (lastTagValue.option_id === option_id) {
|
||||||
isOpen = false;
|
isOpen = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (tagValue) {
|
|
||||||
const response = await api.put(`/v1/cards/${card.id}/tags/${projectTag.id}`, {
|
|
||||||
option_id,
|
|
||||||
value: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.status !== status.NoContent) {
|
if (tagValue) {
|
||||||
processError(response, 'Failed to update tag');
|
await updateCardTagApi(card.id, projectTag.id, option_id, tagValue.value);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
card.tags = card.tags.map((t) => {
|
card.tags = card.tags.map((t) => {
|
||||||
if (t.tag_id === projectTag.id) {
|
if (t.tag_id === projectTag.id) {
|
||||||
|
@ -70,7 +64,7 @@
|
||||||
cards.reload();
|
cards.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteOption(_: number | undefined) {
|
async function deleteOption() {
|
||||||
const response = await api.delete(`/v1/cards/${card.id}/tags/${projectTag.id}`);
|
const response = await api.delete(`/v1/cards/${card.id}/tags/${projectTag.id}`);
|
||||||
|
|
||||||
if (response.status !== status.NoContent) {
|
if (response.status !== status.NoContent) {
|
||||||
|
@ -107,7 +101,7 @@
|
||||||
{#if tagValue}
|
{#if tagValue}
|
||||||
<span class="tag">
|
<span class="tag">
|
||||||
{tagOption?.value}
|
{tagOption?.value}
|
||||||
<button class="real" on:click={() => deleteOption(tagValue?.option_id)}>✗</button>
|
<button class="real" on:click={() => deleteOption()}>✗</button>
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,79 +1,51 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { TagOption, Card, MeTag, TagValue } from '../../stores/interfaces';
|
import type { Card, TagValue } from '../../stores/interfaces';
|
||||||
import { cards, currentDraggedCard } from '../../stores/smallStore';
|
import { cards, currentDraggedCard } from '../../stores/smallStore';
|
||||||
import api, { processError } from '../../utils/api';
|
|
||||||
import status from '../../utils/status';
|
|
||||||
import CardC from './card/card.svelte';
|
import CardC from './card/card.svelte';
|
||||||
import AddIcon from '../icons/addIcon.svelte';
|
import AddIcon from '../icons/addIcon.svelte';
|
||||||
import projectTags from '../../stores/projectTags';
|
import projectTags from '../../stores/projectTags';
|
||||||
import { updateTagAPI as updateTagOptionAPI } from '../../api/tags';
|
import { updateTagAPI as updateTagOptionAPI } from '../../api/tags';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
|
import { createCardTagApi, deleteCardTagApi, updateCardTagApi } from '../../api/cards';
|
||||||
|
|
||||||
export let projectId: number;
|
export let projectId: number;
|
||||||
export let editable: boolean = true;
|
export let optionId: number | null = null;
|
||||||
export let option: TagOption;
|
export let primary_tag_id: number | null = null;
|
||||||
|
export let title: string;
|
||||||
export let columnCards: Card[] = [];
|
export let columnCards: Card[] = [];
|
||||||
|
|
||||||
let lastOptionValue = option.value;
|
let lastTitle = title;
|
||||||
|
|
||||||
async function onDrop(e: DragEvent) {
|
async function onDrop(e: DragEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if ($currentDraggedCard && $currentDraggedCard.tags) {
|
if (!$currentDraggedCard || !$currentDraggedCard.tags) return;
|
||||||
for (let tag of $currentDraggedCard.tags) {
|
for (let tag of $currentDraggedCard.tags) {
|
||||||
if (tag.tag_id == option.tag_id) {
|
if (tag.tag_id !== primary_tag_id) continue;
|
||||||
|
if (tag.option_id == optionId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (tag.option_id == option.id) return;
|
if (tag.option_id && optionId) await deleteCardTagApi(tag.card_id, tag.tag_id);
|
||||||
// DELETE
|
else if (tag.option_id && optionId)
|
||||||
if (tag.option_id !== -1 && option.id === -1) {
|
await createCardTagApi(tag.card_id, tag.tag_id, tag.option_id, tag.value);
|
||||||
const response = await api.delete(`/v1/cards/${tag.card_id}/tags/${tag.tag_id}`);
|
else await updateCardTagApi(tag.card_id, tag.tag_id, optionId, tag.value);
|
||||||
|
|
||||||
if (response.status !== status.NoContent) {
|
tag.option_id = optionId;
|
||||||
processError(response, 'Failed to delete tag');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// CREATE
|
|
||||||
else if (tag.option_id == -1 && option.id !== -1) {
|
|
||||||
const response = await api.post(`/v1/cards/${tag.card_id}/tags/${tag.tag_id}`, {
|
|
||||||
value: tag.value,
|
|
||||||
option_id: option.id
|
|
||||||
});
|
|
||||||
if (response.status !== status.Created) {
|
|
||||||
processError(response, 'Failed to create tag');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// UPDATE
|
|
||||||
else {
|
|
||||||
const response = await api.put(`/v1/cards/${tag.card_id}/tags/${tag.tag_id}`, {
|
|
||||||
value: tag.value,
|
|
||||||
option_id: option.id
|
|
||||||
});
|
|
||||||
if (response.status !== status.NoContent) {
|
|
||||||
processError(response, 'Failed to update tag');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tag.option_id = option.id;
|
|
||||||
cards.reload();
|
cards.reload();
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
currentDraggedCard.set(null);
|
currentDraggedCard.set(null);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async function addCard() {
|
async function addCard() {
|
||||||
const tags: TagValue[] = [];
|
const tags: TagValue[] = [];
|
||||||
for (let tag of Object.values(get(projectTags))) {
|
for (let tag of Object.values(get(projectTags))) {
|
||||||
if (tag.id === option.tag_id) {
|
if (tag.id === primary_tag_id) {
|
||||||
tags.push({
|
tags.push({
|
||||||
card_id: -1,
|
card_id: -1,
|
||||||
tag_id: tag.id,
|
tag_id: tag.id,
|
||||||
option_id: option.id,
|
option_id: optionId,
|
||||||
value: ''
|
value: null
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,15 +65,20 @@
|
||||||
>
|
>
|
||||||
<header>
|
<header>
|
||||||
<input
|
<input
|
||||||
bind:value={option.value}
|
bind:value={title}
|
||||||
type="text"
|
type="text"
|
||||||
on:blur={async () => {
|
on:blur={async () => {
|
||||||
if (lastOptionValue === option.value) return;
|
if (lastTitle === title) return;
|
||||||
await updateTagOptionAPI(option);
|
if (!optionId || !primary_tag_id) return;
|
||||||
lastOptionValue = option.value;
|
await updateTagOptionAPI({
|
||||||
|
id: optionId,
|
||||||
|
tag_id: primary_tag_id,
|
||||||
|
value: title
|
||||||
|
});
|
||||||
|
lastTitle = title;
|
||||||
cards.reload();
|
cards.reload();
|
||||||
}}
|
}}
|
||||||
disabled={!editable}
|
disabled={optionId === null}
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
<span>{columnCards.length}</span>
|
<span>{columnCards.length}</span>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
export let isOpen = false;
|
export let isOpen = false;
|
||||||
export let choices: { id: number; value: string }[] = [];
|
export let choices: { id: number; value: string }[] = [];
|
||||||
export let onChoice = (id: number) => {};
|
export let onChoice = (id: number) => {};
|
||||||
export let currentChoice: number;
|
export let currentChoice: number | null;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Menu {isOpen}>
|
<Menu {isOpen}>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { get } from 'svelte/store';
|
|
||||||
import type { Project, TagValue, View } from '../../stores/interfaces';
|
import type { Project, TagValue, View } from '../../stores/interfaces';
|
||||||
import { cards, currentView, views } from '../../stores/smallStore';
|
import { cards, currentView, views } from '../../stores/smallStore';
|
||||||
import projectTags from '../../stores/projectTags';
|
import projectTags from '../../stores/projectTags';
|
||||||
|
@ -9,19 +8,6 @@
|
||||||
export let view: View;
|
export let view: View;
|
||||||
let groupMenuOpen = false;
|
let groupMenuOpen = false;
|
||||||
|
|
||||||
function getEmptyTags(): TagValue[] {
|
|
||||||
const tags: TagValue[] = [];
|
|
||||||
for (let tag of Object.values(get(projectTags))) {
|
|
||||||
tags.push({
|
|
||||||
card_id: -1,
|
|
||||||
tag_id: tag.id,
|
|
||||||
option_id: -1,
|
|
||||||
value: ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return tags;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setGroup(id: number): Promise<boolean> {
|
async function setGroup(id: number): Promise<boolean> {
|
||||||
if ($currentView == null) return false;
|
if ($currentView == null) return false;
|
||||||
|
|
||||||
|
@ -44,7 +30,7 @@
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
on:click={() => (groupMenuOpen = !groupMenuOpen)}
|
on:click={() => (groupMenuOpen = !groupMenuOpen)}
|
||||||
class:defined={$currentView?.primary_tag_id !== -1}>Group</button
|
class:defined={$currentView?.primary_tag_id}>Group</button
|
||||||
>
|
>
|
||||||
<GroupMenu
|
<GroupMenu
|
||||||
isOpen={groupMenuOpen}
|
isOpen={groupMenuOpen}
|
||||||
|
@ -59,7 +45,7 @@
|
||||||
<button class:disabled={true}>Sub-group</button>
|
<button class:disabled={true}>Sub-group</button>
|
||||||
<button class:disabled={true}>Filter</button>
|
<button class:disabled={true}>Filter</button>
|
||||||
<button class:disabled={true}>Sort</button>
|
<button class:disabled={true}>Sort</button>
|
||||||
<button id="newButton" on:click={async () => cards.add(project.id, getEmptyTags())}>New</button>
|
<button id="newButton" on:click={async () => cards.add(project.id, [])}>New</button>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|
|
@ -29,10 +29,12 @@
|
||||||
<Header {project} {view} />
|
<Header {project} {view} />
|
||||||
{#if cards}
|
{#if cards}
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
{#if view.primary_tag_id !== -1}
|
{#if view.primary_tag_id}
|
||||||
{#each $projectTags[view.primary_tag_id].options as option}
|
{#each $projectTags[view.primary_tag_id].options as option}
|
||||||
<Column
|
<Column
|
||||||
{option}
|
optionId={option.id}
|
||||||
|
primary_tag_id={view.primary_tag_id}
|
||||||
|
title={option.value}
|
||||||
columnCards={$cards.filter((c) =>
|
columnCards={$cards.filter((c) =>
|
||||||
c.tags.map((t) => t.option_id).includes(option.id)
|
c.tags.map((t) => t.option_id).includes(option.id)
|
||||||
)}
|
)}
|
||||||
|
@ -41,21 +43,16 @@
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
<Column
|
<Column
|
||||||
option={{
|
primary_tag_id={view.primary_tag_id}
|
||||||
id: -1,
|
title={view.primary_tag_id
|
||||||
tag_id: view.primary_tag_id,
|
|
||||||
value:
|
|
||||||
view.primary_tag_id !== -1
|
|
||||||
? `No ${$projectTags[view.primary_tag_id].title}`
|
? `No ${$projectTags[view.primary_tag_id].title}`
|
||||||
: 'No groups'
|
: 'No groups'}
|
||||||
}}
|
columnCards={view.primary_tag_id
|
||||||
columnCards={view.primary_tag_id !== -1
|
|
||||||
? $cards.filter(
|
? $cards.filter(
|
||||||
(c) => !c.tags.map((t) => t.tag_id).includes(view?.primary_tag_id || -2)
|
(c) => !c.tags.map((t) => t.tag_id).includes(view?.primary_tag_id || -2)
|
||||||
)
|
)
|
||||||
: $cards}
|
: $cards}
|
||||||
projectId={project.id}
|
projectId={project.id}
|
||||||
editable={false}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
if (!$views) return;
|
if (!$views) return;
|
||||||
|
|
||||||
const primaryTagId =
|
const primaryTagId =
|
||||||
$currentView?.primary_tag_id || Object.values($projectTags).find((t) => true)?.id || -1;
|
$currentView?.primary_tag_id || Object.values($projectTags).find((t) => true)?.id || null;
|
||||||
|
|
||||||
const newView = await views.add(project.id, 'New view', primaryTagId);
|
const newView = await views.add(project.id, 'New view', primaryTagId);
|
||||||
|
|
||||||
|
@ -233,10 +233,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
span {
|
|
||||||
padding-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
|
|
|
@ -14,8 +14,8 @@ export interface Card {
|
||||||
export interface TagValue {
|
export interface TagValue {
|
||||||
card_id: number;
|
card_id: number;
|
||||||
tag_id: number;
|
tag_id: number;
|
||||||
option_id: number;
|
option_id: number | null;
|
||||||
value: string;
|
value: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MeTag {
|
export interface MeTag {
|
||||||
|
@ -35,9 +35,11 @@ export interface TagOption {
|
||||||
export interface View {
|
export interface View {
|
||||||
id: number;
|
id: number;
|
||||||
project_id: number;
|
project_id: number;
|
||||||
primary_tag_id: number;
|
primary_tag_id: number | null;
|
||||||
secondary_tag_id: number;
|
secondary_tag_id: number | null;
|
||||||
title: string;
|
title: string;
|
||||||
|
sort_tag_id: number | null;
|
||||||
|
sort_direction: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseCard(c: any) {
|
export function parseCard(c: any) {
|
||||||
|
|
|
@ -26,7 +26,7 @@ export const currentView = (() => {
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
export const currentModalCard = writable(-1);
|
export const currentModalCard = writable(null as number | null);
|
||||||
|
|
||||||
export const currentDraggedCard = writable(null as Card | null);
|
export const currentDraggedCard = writable(null as Card | null);
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ export const cards = (() => {
|
||||||
remove: async (card: Card) => {
|
remove: async (card: Card) => {
|
||||||
await deleteCardApi(card.id).then(() => {
|
await deleteCardApi(card.id).then(() => {
|
||||||
update((cards) => cards.filter((c) => c.id !== card.id));
|
update((cards) => cards.filter((c) => c.id !== card.id));
|
||||||
currentModalCard.set(-1);
|
currentModalCard.set(null);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
edit: async (card: Card): Promise<boolean> => {
|
edit: async (card: Card): Promise<boolean> => {
|
||||||
|
@ -82,7 +82,7 @@ export const views = (() => {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
const add = async (projectId: number, title: string, primaryTagId: number): Promise<View> => {
|
const add = async (projectId: number, title: string, primaryTagId: number | null): Promise<View> => {
|
||||||
const response = await api.post(`/v1/views`, {
|
const response = await api.post(`/v1/views`, {
|
||||||
title,
|
title,
|
||||||
project_id: projectId,
|
project_id: projectId,
|
||||||
|
@ -99,7 +99,9 @@ export const views = (() => {
|
||||||
title: title,
|
title: title,
|
||||||
project_id: projectId,
|
project_id: projectId,
|
||||||
primary_tag_id: primaryTagId,
|
primary_tag_id: primaryTagId,
|
||||||
secondary_tag_id: 0
|
secondary_tag_id: null,
|
||||||
|
sort_tag_id: null,
|
||||||
|
sort_direction: null
|
||||||
};
|
};
|
||||||
|
|
||||||
update((views) => [...views, view]);
|
update((views) => [...views, view]);
|
||||||
|
|
Loading…
Reference in New Issue