Create & edit views

This commit is contained in:
Brieuc Dubois 2024-01-02 02:05:24 +01:00
parent 5bc43aaba2
commit bc566280a6
5 changed files with 214 additions and 22 deletions

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 576 512">
<path
d="M402.6 83.2l90.2 90.2c3.8 3.8 3.8 10 0 13.8L274.4 405.6l-92.8 10.3c-12.4 1.4-22.9-9.1-21.5-21.5l10.3-92.8L388.8 83.2c3.8-3.8 10-3.8 13.8 0zm162-22.9l-48.8-48.8c-15.2-15.2-39.9-15.2-55.2 0l-35.4 35.4c-3.8 3.8-3.8 10 0 13.8l90.2 90.2c3.8 3.8 10 3.8 13.8 0l35.4-35.4c15.2-15.3 15.2-40 0-55.2zM384 346.2V448H64V128h229.8c3.2 0 6.2-1.3 8.5-3.5l40-40c7.6-7.6 2.2-20.5-8.5-20.5H48C21.5 64 0 85.5 0 112v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V306.2c0-10.7-12.9-16-20.5-8.5l-40 40c-2.2 2.3-3.5 5.3-3.5 8.5z"
fill="#fff"
/>
</svg>

After

Width:  |  Height:  |  Size: 632 B

View File

@ -53,8 +53,8 @@
<div> <div>
<button class:disabled={true}>Sub-group</button> <button class:disabled={true}>Sub-group</button>
</div> </div>
<button>Filter</button> <button class:disabled={true}>Filter</button>
<button>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, getEmptyTags())}>New</button>
</nav> </nav>
</header> </header>

View File

@ -3,7 +3,7 @@
import Column from './column.svelte'; import Column from './column.svelte';
import type { Project, View } from '../../stores/interfaces'; import type { Project, View } from '../../stores/interfaces';
import projectTags from '../../stores/projectTags'; import projectTags from '../../stores/projectTags';
import { cards, currentView } from '../../stores/smallStore'; import { cards, currentView, views } from '../../stores/smallStore';
import Header from './header.svelte'; import Header from './header.svelte';
export let project: Project; export let project: Project;

View File

@ -2,24 +2,48 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import api, { processError } from '../utils/api'; import api, { processError } from '../utils/api';
import type { Project, View } from '../stores/interfaces'; import type { Project, View } from '../stores/interfaces';
import { currentView } from '../stores/smallStore'; import { currentView, views } from '../stores/smallStore';
import ViewIcon from './icons/viewIcon.svelte'; import ViewIcon from './icons/viewIcon.svelte';
import projectTags from '../stores/projectTags';
import EditIcon from './icons/editIcon.svelte';
export let project: Project; export let project: Project;
let views: View[];
let viewEditId: number;
let viewEditValue: string;
onMount(async () => { onMount(async () => {
const response = await api.get(`/v1/projects/${project.id}/views`); console.log('aaa');
await views.init(project.id);
if (response.status !== 200) { if ($views.length > 0) currentView.set($views[0]);
processError(response, 'Failed to fetch views'); });
return;
async function newView() {
if (!$views) return;
const primaryTagId =
$currentView?.primary_tag_id || Object.values($projectTags).find((t) => true)?.id || -1;
const newView = await views.add(project.id, 'New view', primaryTagId);
if (!newView) return;
currentView.set(newView);
viewEditId = newView.id;
viewEditValue = newView.title;
document.getElementById(`viewTitle-${newView.id}`)?.focus();
} }
views = response.data; async function saveView(view: View) {
if (!view || !$views.includes(view)) return;
if (viewEditId === view.id && viewEditValue !== view.title) {
if (!(await views.update(view))) return;
}
if (views.length > 0) currentView.set(views[0]); viewEditId = -1;
}); viewEditValue = '';
}
</script> </script>
<nav> <nav>
@ -32,15 +56,46 @@
{#if views} {#if views}
<h2>{project.title}</h2> <h2>{project.title}</h2>
<ul> <ul>
{#each views as view} {#each $views as view}
<li> <!-- svelte-ignore a11y-no-noninteractive-element-to-interactive-role -->
<ViewIcon /> <li
<!-- on:click={() => { on:click={() => currentView.set(view)}
role="button"
tabindex="0"
on:keydown={(e) => {
if (e.key === 'Enter') {
currentView.set(view); currentView.set(view);
}} --> }
<span> }}
{view.title} class:active={$currentView === view}
</span> >
<ViewIcon />
<input
type="text"
readonly={viewEditId !== view.id}
bind:value={view.title}
class:inEdit={viewEditId === view.id}
on:blur={() => saveView(view)}
id="viewTitle-{view.id}"
on:keydown={(e) => {
if (e.key === 'Enter') {
saveView(view);
}
}}
/>
<button
on:click={() => {
if (viewEditId === view.id) {
saveView(view);
} else {
viewEditId = view.id;
viewEditValue = view.title;
document.getElementById(`viewTitle-${view.id}`)?.focus();
}
}}
>
<EditIcon />
</button>
</li> </li>
{/each} {/each}
</ul> </ul>
@ -49,7 +104,19 @@
</div> </div>
<div> <div>
<div class="separator"></div> <div class="separator"></div>
<div id="newView" on:click={() => {}}>+ New view</div> <div
id="newView"
on:click={newView}
role="button"
tabindex="0"
on:keydown={(e) => {
if (e.key === 'Enter') {
newView();
}
}}
>
+ New view
</div>
</div> </div>
</nav> </nav>
@ -93,9 +160,46 @@
padding: 10px; padding: 10px;
} }
li {
cursor: pointer;
padding: 0 10px;
}
input {
padding: 10px;
border-radius: 5px;
background-color: transparent;
border: none;
color: inherit;
font-size: 17px;
width: 60%;
&:focus {
outline: 0;
}
&.inEdit {
background-color: #fff5;
}
}
span { span {
padding-left: 10px; padding-left: 10px;
} }
button {
background-color: transparent;
border: none;
color: inherit;
font-size: 17px;
padding: 5px 0;
float: right;
cursor: pointer;
}
.active {
background-color: #fff1;
}
} }
.separator { .separator {

View File

@ -57,3 +57,85 @@ export const cards = (() => {
} }
}; };
})(); })();
export const views = (() => {
const { subscribe, set, update } = writable([] as View[]);
const init = async (projectId: number): Promise<boolean> => {
const response = await api.get(`/v1/projects/${projectId}/views`);
if (response.status !== status.OK) {
processError(response, 'Failed to get views');
return false;
}
set(response.data);
return true;
};
const add = async (projectId: number, title: string, primaryTagId: number): Promise<View> => {
const response = await api.post(`/v1/views`, {
title,
project_id: projectId,
primary_tag_id: primaryTagId
});
if (response.status !== status.Created) {
processError(response, 'Failed to add view');
return Promise.reject();
}
const view: View = {
id: response.data.id,
title: title,
project_id: projectId,
primary_tag_id: primaryTagId,
secondary_tag_id: 0
};
update((views) => [...views, view]);
return view;
};
const remove = async (view: View): Promise<boolean> => {
const response = await api.delete(`/v1/views/${view.id}`);
if (response.status !== status.NoContent) {
processError(response, 'Failed to delete view');
return false;
}
update((views) => views.filter((v) => v.id !== view.id));
return true;
};
const edit = async (view: View): Promise<boolean> => {
if (view.title === '') {
if (confirm('Are you sure you want to delete this view?')) {
return remove(view);
} else {
return false;
}
}
const response = await api.put(`/v1/views/${view.id}`, view);
if (response.status !== status.NoContent) {
processError(response, 'Failed to update view');
return false;
}
update((views) => views.map((v) => (v.id === view.id ? view : v)));
return true;
};
return {
subscribe,
init,
add,
remove,
update: edit
};
})();