Replace -1 by nullable values

This commit is contained in:
Brieuc Dubois 2024-01-04 02:08:48 +01:00
parent e2e87ce9ec
commit 98e54f94a6
17 changed files with 145 additions and 165 deletions

View File

@ -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(&currentVersion)
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
} }

View File

@ -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
} }

View File

@ -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"`
} }

View File

@ -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"`
}

View File

@ -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"`
} }

View File

@ -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",

View File

@ -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) {

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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}>

View File

@ -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>

View File

@ -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}

View File

@ -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;

View File

@ -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) {

View File

@ -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]);