Compare commits
No commits in common. "9bb9041580ab7e04de42a280bcbfa26f0310cf5a" and "b6e36fdf00f5afa0f1b3f89c0816d00c085f08ee" have entirely different histories.
9bb9041580
...
b6e36fdf00
|
@ -1,76 +0,0 @@
|
||||||
package db
|
|
||||||
|
|
||||||
import "git.bhasher.com/bhasher/focus/backend/types"
|
|
||||||
|
|
||||||
func CreateFilter(filter types.Filter) (int, error) {
|
|
||||||
res, err := db.Exec("INSERT INTO filters (view_id, tag_id, filter_type, option_id) VALUES (?, ?, ?, ?)", filter.ViewID, filter.TagID, filter.FilterType, filter.OptionID)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := res.LastInsertId()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return int(id), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetViewFilters(viewID int) ([]types.Filter, error) {
|
|
||||||
rows, err := db.Query("SELECT * FROM filters WHERE view_id = ?", viewID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var filters []types.Filter
|
|
||||||
for rows.Next() {
|
|
||||||
var f types.Filter
|
|
||||||
if err := rows.Scan(&f.ID, &f.ViewID, &f.TagID, &f.FilterType, &f.OptionID); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
filters = append(filters, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = rows.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return filters, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetFilter(id int) (*types.Filter, error) {
|
|
||||||
rows, err := db.Query("SELECT * FROM filters WHERE id = ?", id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
if !rows.Next() {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var f types.Filter
|
|
||||||
rows.Scan(&f.ID, &f.ViewID, &f.TagID, &f.FilterType, &f.OptionID)
|
|
||||||
|
|
||||||
return &f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpdateFilter(f types.Filter) (int64, error) {
|
|
||||||
res, err := db.Exec("UPDATE filters SET view_id = ?, tag_id = ?, filter_type = ?, option_id = ? WHERE id = ?", f.ViewID, f.TagID, f.FilterType, f.OptionID, f.ID)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.RowsAffected()
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteFilter(id int) (int64, error) {
|
|
||||||
res, err := db.Exec("DELETE FROM filters WHERE id = ?", id)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.RowsAffected()
|
|
||||||
}
|
|
|
@ -67,17 +67,6 @@ func InitDB(driver string, connStr string) error {
|
||||||
FOREIGN KEY(secondary_tag_id) REFERENCES tags(id)
|
FOREIGN KEY(secondary_tag_id) REFERENCES tags(id)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS filters (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
view_id INTEGER NOT NULL,
|
|
||||||
tag_id INTEGER NOT NULL,
|
|
||||||
filter_type INTEGER NOT NULL,
|
|
||||||
option_id INTEGER,
|
|
||||||
FOREIGN KEY(view_id) REFERENCES views(id),
|
|
||||||
FOREIGN KEY(tag_id) REFERENCES tags(id),
|
|
||||||
FOREIGN KEY(option_id) REFERENCES tagsoptions(id)
|
|
||||||
);
|
|
||||||
|
|
||||||
INSERT INTO schema_version (version)
|
INSERT INTO schema_version (version)
|
||||||
SELECT ? WHERE NOT EXISTS (SELECT 1 FROM schema_version);
|
SELECT ? WHERE NOT EXISTS (SELECT 1 FROM schema_version);
|
||||||
`, DB_VERSION)
|
`, DB_VERSION)
|
||||||
|
|
|
@ -16,26 +16,20 @@ func CreateView(v types.View) (int, error) {
|
||||||
return int(id), nil
|
return int(id), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetProjectViews(projectID int) ([]types.FullView, error) {
|
func GetProjectViews(projectID int) ([]types.View, error) {
|
||||||
rows, err := db.Query("SELECT * FROM views WHERE project_id = ?", projectID)
|
rows, err := db.Query("SELECT * FROM views WHERE project_id = ?", projectID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
var views []types.FullView
|
var views []types.View
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var v types.FullView
|
var v types.View
|
||||||
if err := rows.Scan(&v.ID, &v.ProjectID, &v.PrimaryTagID, &v.SecondaryTagID, &v.Title, &v.SortTagID, &v.SortDirection); 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
|
||||||
}
|
}
|
||||||
|
|
||||||
filters, err := GetViewFilters(v.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
v.Filters = filters
|
|
||||||
|
|
||||||
views = append(views, v)
|
views = append(views, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +40,7 @@ func GetProjectViews(projectID int) ([]types.FullView, error) {
|
||||||
return views, nil
|
return views, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetView(id int) (*types.FullView, error) {
|
func GetView(id int) (*types.View, error) {
|
||||||
rows, err := db.Query("SELECT * FROM views WHERE id = ?", id)
|
rows, err := db.Query("SELECT * FROM views WHERE id = ?", id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -57,15 +51,9 @@ func GetView(id int) (*types.FullView, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var v types.FullView
|
var v types.View
|
||||||
rows.Scan(&v.ID, &v.ProjectID, v.PrimaryTagID, v.SecondaryTagID, v.Title, v.SortTagID, v.SortDirection)
|
rows.Scan(&v.ID, &v.ProjectID, v.PrimaryTagID, v.SecondaryTagID, v.Title, v.SortTagID, v.SortDirection)
|
||||||
|
|
||||||
filters, err := GetViewFilters(id)
|
|
||||||
if(err != nil) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
v.Filters = filters
|
|
||||||
|
|
||||||
return &v, nil
|
return &v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,13 +74,3 @@ func DeleteView(id int) (int64, error) {
|
||||||
|
|
||||||
return res.RowsAffected()
|
return res.RowsAffected()
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExistView(id int) (bool, error) {
|
|
||||||
var count int
|
|
||||||
err := db.QueryRow("SELECT COUNT(*) FROM views WHERE id = ?", id).Scan(&count)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return count > 0, nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,131 +0,0 @@
|
||||||
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 filtersRouter(router fiber.Router) error {
|
|
||||||
router.Post("/", CreateFilter)
|
|
||||||
router.Get("/:id", GetFilter)
|
|
||||||
router.Put("/:id", UpdateFilter)
|
|
||||||
router.Delete("/:id", DeleteFilter)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateFilter(c *fiber.Ctx) error {
|
|
||||||
filter := types.Filter{}
|
|
||||||
if err := c.BodyParser(&filter); err != nil {
|
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
||||||
"error": "Cannot parse request",
|
|
||||||
"trace": fmt.Sprint(err),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
exist, err := db.ExistView(filter.ViewID)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
||||||
"error": "Error finding view",
|
|
||||||
"trace": fmt.Sprint(err),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if !exist {
|
|
||||||
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "View not found"})
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := db.CreateFilter(filter)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
||||||
"error": "Cannot create filter",
|
|
||||||
"trace": fmt.Sprint(err),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
|
|
||||||
"id": id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetFilter(c *fiber.Ctx) error {
|
|
||||||
id, err := strconv.Atoi(c.Params("id"))
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid filter ID"})
|
|
||||||
}
|
|
||||||
|
|
||||||
filter, err := db.GetFilter(id)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
||||||
"error": "Cannot retrieve filter",
|
|
||||||
"trace": fmt.Sprint(err),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if filter == nil {
|
|
||||||
return c.SendStatus(fiber.StatusNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).JSON(filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpdateFilter(c *fiber.Ctx) error {
|
|
||||||
id, err := strconv.Atoi(c.Params("id"))
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid filter ID"})
|
|
||||||
}
|
|
||||||
|
|
||||||
filter := types.Filter{ID: id}
|
|
||||||
if err := c.BodyParser(&filter); err != nil {
|
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
||||||
"error": "Cannot parse request",
|
|
||||||
"trace": fmt.Sprint(err),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
exist, err := db.ExistView(filter.ViewID)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
||||||
"error": "Error finding view",
|
|
||||||
"trace": fmt.Sprint(err),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if !exist {
|
|
||||||
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "View not found"})
|
|
||||||
}
|
|
||||||
|
|
||||||
count, err := db.UpdateFilter(filter)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
||||||
"error": "Cannot update filter",
|
|
||||||
"trace": fmt.Sprint(err),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if count == 0 {
|
|
||||||
return c.SendStatus(fiber.StatusNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.SendStatus(fiber.StatusNoContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteFilter(c *fiber.Ctx) error {
|
|
||||||
id, err := strconv.Atoi(c.Params("id"))
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid filter ID"})
|
|
||||||
}
|
|
||||||
|
|
||||||
count, err := db.DeleteFilter(id)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
||||||
"error": "Cannot delete filter",
|
|
||||||
"trace": fmt.Sprint(err),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if count == 0 {
|
|
||||||
return c.SendStatus(fiber.StatusNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.SendStatus(fiber.StatusNoContent)
|
|
||||||
}
|
|
|
@ -11,7 +11,6 @@ func v1Router(router fiber.Router) error {
|
||||||
cardsRouter(router.Group("/cards"))
|
cardsRouter(router.Group("/cards"))
|
||||||
tagsRouter(router.Group("/tags"))
|
tagsRouter(router.Group("/tags"))
|
||||||
viewsRouter(router.Group("/views"))
|
viewsRouter(router.Group("/views"))
|
||||||
filtersRouter(router.Group("/filters"))
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ func viewsRouter(router fiber.Router) error {
|
||||||
router.Get("/:id", GetView)
|
router.Get("/:id", GetView)
|
||||||
router.Put("/:id", UpdateView)
|
router.Put("/:id", UpdateView)
|
||||||
router.Delete("/:id", DeleteView)
|
router.Delete("/:id", DeleteView)
|
||||||
router.Get("/:id/filters", GetView)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,20 +108,3 @@ func DeleteView(c *fiber.Ctx) error {
|
||||||
|
|
||||||
return c.SendStatus(fiber.StatusNoContent)
|
return c.SendStatus(fiber.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetViewFilters(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"})
|
|
||||||
}
|
|
||||||
|
|
||||||
filters, err := db.GetViewFilters(id)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
||||||
"error": "Cannot retrieve view filters",
|
|
||||||
"trace": fmt.Sprint(err),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).JSON(filters)
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
package types
|
|
||||||
|
|
||||||
type Filter struct {
|
|
||||||
ID int `json:"id"`
|
|
||||||
ViewID int `json:"view_id"`
|
|
||||||
TagID int `json:"tag_id"`
|
|
||||||
FilterType int `json:"filter_type"`
|
|
||||||
OptionID int `json:"option_id"`
|
|
||||||
}
|
|
|
@ -9,14 +9,3 @@ type View struct {
|
||||||
SortTagID *int `json:"sort_tag_id"`
|
SortTagID *int `json:"sort_tag_id"`
|
||||||
SortDirection *int `json:"sort_direction"`
|
SortDirection *int `json:"sort_direction"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FullView struct {
|
|
||||||
ID int `json:"id"`
|
|
||||||
ProjectID int `json:"project_id"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
PrimaryTagID *int `json:"primary_tag_id"`
|
|
||||||
SecondaryTagID *int `json:"secondary_tag_id"`
|
|
||||||
SortTagID *int `json:"sort_tag_id"`
|
|
||||||
SortDirection *int `json:"sort_direction"`
|
|
||||||
Filters []Filter `json:"filters"`
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
import api, { processError } from '$lib/utils/api';
|
|
||||||
import status from '$lib/utils/status';
|
|
||||||
|
|
||||||
async function create(
|
|
||||||
viewId: number,
|
|
||||||
projectTagId: number,
|
|
||||||
filterType: number,
|
|
||||||
tagOptionId: number | null
|
|
||||||
): Promise<number | null> {
|
|
||||||
const response = await api.post(`/v1/filters`, {
|
|
||||||
view_id: viewId,
|
|
||||||
tag_id: projectTagId,
|
|
||||||
filter_type: filterType,
|
|
||||||
option_id: tagOptionId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.status !== status.Created) {
|
|
||||||
processError(response, 'Failed to create filter');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.data.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function update(
|
|
||||||
filterId: number,
|
|
||||||
viewId: number,
|
|
||||||
projectTagId: number,
|
|
||||||
filterType: number,
|
|
||||||
tagOptionId: number | null
|
|
||||||
): Promise<boolean> {
|
|
||||||
const response = await api.put(`/v1/filters/${filterId}`, {
|
|
||||||
view_id: viewId,
|
|
||||||
tag_id: projectTagId,
|
|
||||||
filter_type: filterType,
|
|
||||||
option_id: tagOptionId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.status !== status.NoContent) {
|
|
||||||
processError(response, 'Failed to update filter');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function delete_(filterId: number): Promise<boolean> {
|
|
||||||
const response = await api.delete(`/v1/filters/${filterId}`);
|
|
||||||
|
|
||||||
if (response.status !== status.NoContent) {
|
|
||||||
processError(response, 'Failed to delete filter');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
create,
|
|
||||||
update,
|
|
||||||
delete: delete_
|
|
||||||
};
|
|
|
@ -1,5 +1,5 @@
|
||||||
import Filter from '$lib/types/Filter';
|
|
||||||
import type Project from '$lib/types/Project';
|
import type Project from '$lib/types/Project';
|
||||||
|
import type View from '$lib/types/View';
|
||||||
import api, { processError } from '$lib/utils/api';
|
import api, { processError } from '$lib/utils/api';
|
||||||
import status from '$lib/utils/status';
|
import status from '$lib/utils/status';
|
||||||
|
|
||||||
|
@ -53,20 +53,8 @@ async function delete_(viewId: number): Promise<boolean> {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getFilters(viewId: number): Promise<Filter[]> {
|
|
||||||
const response = await api.get(`/v1/views/${viewId}/filters`);
|
|
||||||
|
|
||||||
if (response.status !== status.OK) {
|
|
||||||
processError(response, 'Failed to get view filters');
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return Filter.parseAll(response.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
create,
|
create,
|
||||||
update,
|
update,
|
||||||
delete: delete_,
|
delete: delete_
|
||||||
getFilters
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import currentView from '$lib/stores/currentView';
|
|
||||||
import type Filter from '$lib/types/Filter';
|
|
||||||
import FilterMenuItem from './FilterMenuItem.svelte';
|
|
||||||
import Menu from './Menu.svelte';
|
|
||||||
|
|
||||||
export let isOpen = false;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if $currentView}
|
|
||||||
<Menu bind:isOpen>
|
|
||||||
{#each $currentView.filters as filter}
|
|
||||||
<FilterMenuItem {filter} />
|
|
||||||
{/each}
|
|
||||||
<FilterMenuItem />
|
|
||||||
</Menu>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style lang="less">
|
|
||||||
</style>
|
|
|
@ -1,190 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import currentView from '$lib/stores/currentView';
|
|
||||||
import Filter from '$lib/types/Filter';
|
|
||||||
import ProjectTag, { projectTags } from '$lib/types/ProjectTag';
|
|
||||||
import type TagOption from '$lib/types/TagOption';
|
|
||||||
import TrashIcon from '../icons/TrashIcon.svelte';
|
|
||||||
import Menu from './Menu.svelte';
|
|
||||||
|
|
||||||
export let filter: Filter | null = null;
|
|
||||||
|
|
||||||
let isProjectTagOpen = false;
|
|
||||||
let isFilterTypeOpen = false;
|
|
||||||
let isOptionOpen = false;
|
|
||||||
|
|
||||||
async function selectProjectTag(projectTag: ProjectTag) {
|
|
||||||
if (!$currentView) return;
|
|
||||||
if (!filter) {
|
|
||||||
await $currentView?.addFilter(projectTag, 0, null);
|
|
||||||
currentView.reload();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filter.projectTag.id !== projectTag.id) {
|
|
||||||
const res = await filter.setProjectTag(projectTag);
|
|
||||||
if (!res) return;
|
|
||||||
currentView.reload();
|
|
||||||
}
|
|
||||||
isProjectTagOpen = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function selectFilterType(filterType: number) {
|
|
||||||
if (!filter) return;
|
|
||||||
|
|
||||||
if (filter.filterType !== filterType) {
|
|
||||||
const res = await filter.setFilterType(filterType);
|
|
||||||
if (!res) return;
|
|
||||||
currentView.reload();
|
|
||||||
}
|
|
||||||
isFilterTypeOpen = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function selectOption(option: TagOption) {
|
|
||||||
if (!filter) return;
|
|
||||||
|
|
||||||
if (filter.tagOption !== option) {
|
|
||||||
const res = await filter.setTagOption(option);
|
|
||||||
if (!res) return;
|
|
||||||
currentView.reload();
|
|
||||||
}
|
|
||||||
isOptionOpen = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteFilter() {
|
|
||||||
if (!filter) return;
|
|
||||||
|
|
||||||
const res = await $currentView?.removeFilter(filter);
|
|
||||||
if (!res) return;
|
|
||||||
currentView.reload();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="item">
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="part"
|
|
||||||
on:click={() => (isProjectTagOpen = !isProjectTagOpen)}
|
|
||||||
on:keydown={(e) => {
|
|
||||||
if (e.key === 'Enter') isProjectTagOpen = !isProjectTagOpen;
|
|
||||||
}}
|
|
||||||
tabindex="0"
|
|
||||||
role="button"
|
|
||||||
>
|
|
||||||
{#if filter}
|
|
||||||
{filter.projectTag.title}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<Menu bind:isOpen={isProjectTagOpen}>
|
|
||||||
{#each $projectTags as projectTag}
|
|
||||||
<button on:click={() => selectProjectTag(projectTag)}>
|
|
||||||
{projectTag.title}
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
</Menu>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="part"
|
|
||||||
on:click={() => (isFilterTypeOpen = !isFilterTypeOpen)}
|
|
||||||
on:keydown={(e) => {
|
|
||||||
if (e.key === 'Enter') isFilterTypeOpen = !isFilterTypeOpen;
|
|
||||||
}}
|
|
||||||
tabindex="0"
|
|
||||||
role="button"
|
|
||||||
>
|
|
||||||
{#if filter}
|
|
||||||
{#if filter.filterType === 0}
|
|
||||||
is
|
|
||||||
{:else if filter.filterType === 1}
|
|
||||||
is not
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{#if filter}
|
|
||||||
<Menu bind:isOpen={isFilterTypeOpen}>
|
|
||||||
<button on:click={() => selectFilterType(0)}> is </button>
|
|
||||||
<button
|
|
||||||
on:click={() => {
|
|
||||||
selectFilterType(1);
|
|
||||||
isFilterTypeOpen = false;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
is not
|
|
||||||
</button>
|
|
||||||
</Menu>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="part"
|
|
||||||
on:click={() => (isOptionOpen = !isOptionOpen)}
|
|
||||||
on:keydown={(e) => {
|
|
||||||
if (e.key === 'Enter') isOptionOpen = !isOptionOpen;
|
|
||||||
}}
|
|
||||||
tabindex="0"
|
|
||||||
role="button"
|
|
||||||
>
|
|
||||||
{#if filter && filter.tagOption}
|
|
||||||
{filter.tagOption.value}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{#if filter && filter.projectTag}
|
|
||||||
<Menu bind:isOpen={isOptionOpen}>
|
|
||||||
{#each filter.projectTag.options as option}
|
|
||||||
<button on:click={() => selectOption(option)}>
|
|
||||||
{option.value}
|
|
||||||
</button>
|
|
||||||
{/each}
|
|
||||||
</Menu>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{#if filter}
|
|
||||||
<div
|
|
||||||
class="delete"
|
|
||||||
tabindex="0"
|
|
||||||
role="button"
|
|
||||||
on:click={() => deleteFilter()}
|
|
||||||
on:keydown={(e) => {
|
|
||||||
if (e.key === 'Enter') deleteFilter();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TrashIcon size={20} />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style lang="less">
|
|
||||||
.item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.part {
|
|
||||||
min-width: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.part,
|
|
||||||
.delete {
|
|
||||||
height: 30px;
|
|
||||||
margin: 5px;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 30px;
|
|
||||||
padding: 0 2px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: #fff2;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete {
|
|
||||||
line-height: 35px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
min-width: 100px;
|
|
||||||
text-align: left;
|
|
||||||
padding: 5px;
|
|
||||||
margin: 2px 5px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -3,10 +3,9 @@
|
||||||
import CardComponent from '../card/Card.svelte';
|
import CardComponent from '../card/Card.svelte';
|
||||||
import AddIcon from '../icons/AddIcon.svelte';
|
import AddIcon from '../icons/AddIcon.svelte';
|
||||||
import type TagOption from '$lib/types/TagOption';
|
import type TagOption from '$lib/types/TagOption';
|
||||||
import ProjectTag, { projectTags } from '$lib/types/ProjectTag';
|
import type ProjectTag from '$lib/types/ProjectTag';
|
||||||
import type Project from '$lib/types/Project';
|
import type Project from '$lib/types/Project';
|
||||||
import currentDraggedCard from '$lib/stores/currentDraggedCard';
|
import currentDraggedCard from '$lib/stores/currentDraggedCard';
|
||||||
import currentView from '$lib/stores/currentView';
|
|
||||||
|
|
||||||
export let project: Project;
|
export let project: Project;
|
||||||
export let option: TagOption | null = null;
|
export let option: TagOption | null = null;
|
||||||
|
@ -44,22 +43,10 @@
|
||||||
const card = await Card.create(project);
|
const card = await Card.create(project);
|
||||||
|
|
||||||
if (!card) return;
|
if (!card) return;
|
||||||
|
if (!primaryTag) return;
|
||||||
|
if (!option) return;
|
||||||
|
|
||||||
if ($currentView?.filters && $currentView.filters.length > 0) {
|
await card.addTag(primaryTag, option, null);
|
||||||
for (const projectTag of $projectTags) {
|
|
||||||
for (const filter of $currentView.filters) {
|
|
||||||
if (projectTag !== filter.projectTag) continue;
|
|
||||||
if (!filter.tagOption) continue;
|
|
||||||
if (filter.filterType !== 0) continue;
|
|
||||||
|
|
||||||
if (await card.addTag(projectTag, filter.tagOption, null)) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (primaryTag && option) {
|
|
||||||
await card.addTag(primaryTag, option, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
cards.reload();
|
cards.reload();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,17 +2,15 @@
|
||||||
import GroupMenu from '$lib/components/menu/GroupMenu.svelte';
|
import GroupMenu from '$lib/components/menu/GroupMenu.svelte';
|
||||||
import SortMenu from '$lib/components/menu/SortMenu.svelte';
|
import SortMenu from '$lib/components/menu/SortMenu.svelte';
|
||||||
import currentView from '$lib/stores/currentView';
|
import currentView from '$lib/stores/currentView';
|
||||||
import Card, { cards } from '$lib/types/Card';
|
import Card from '$lib/types/Card';
|
||||||
import type Project from '$lib/types/Project';
|
import type Project from '$lib/types/Project';
|
||||||
import type ProjectTag from '$lib/types/ProjectTag';
|
import type ProjectTag from '$lib/types/ProjectTag';
|
||||||
import { projectTags } from '$lib/types/ProjectTag';
|
import { projectTags } from '$lib/types/ProjectTag';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import FilterMenu from '../menu/FilterMenu.svelte';
|
|
||||||
|
|
||||||
export let project: Project;
|
export let project: Project;
|
||||||
let groupMenuOpen = false;
|
let groupMenuOpen = false;
|
||||||
let sortMenuOpen = false;
|
let sortMenuOpen = false;
|
||||||
let filterMenuOpen = false;
|
|
||||||
|
|
||||||
async function setGroup(projectTag: ProjectTag): Promise<boolean> {
|
async function setGroup(projectTag: ProjectTag): Promise<boolean> {
|
||||||
const view = get(currentView);
|
const view = get(currentView);
|
||||||
|
@ -35,26 +33,6 @@
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addCard() {
|
|
||||||
const card = await Card.create(project);
|
|
||||||
|
|
||||||
if (!card) return;
|
|
||||||
|
|
||||||
if ($currentView?.filters && $currentView.filters.length > 0) {
|
|
||||||
for (const projectTag of $projectTags) {
|
|
||||||
for (const filter of $currentView.filters) {
|
|
||||||
if (projectTag !== filter.projectTag) continue;
|
|
||||||
if (!filter.tagOption) continue;
|
|
||||||
if (filter.filterType !== 0) continue;
|
|
||||||
|
|
||||||
if (await card.addTag(projectTag, filter.tagOption, null)) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cards.reload();
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
|
@ -76,15 +54,7 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button class:disabled={true}>Sub-group</button>
|
<button class:disabled={true}>Sub-group</button>
|
||||||
<div>
|
<button class:disabled={true}>Filter</button>
|
||||||
<button
|
|
||||||
on:click={() => (filterMenuOpen = !filterMenuOpen)}
|
|
||||||
class:defined={$currentView?.filters && $currentView?.filters.length > 0}
|
|
||||||
>
|
|
||||||
Filter
|
|
||||||
</button>
|
|
||||||
<FilterMenu bind:isOpen={filterMenuOpen} />
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<button on:click={() => (sortMenuOpen = !sortMenuOpen)} class:defined={$currentView?.sortTag}>
|
<button on:click={() => (sortMenuOpen = !sortMenuOpen)} class:defined={$currentView?.sortTag}>
|
||||||
Sort
|
Sort
|
||||||
|
@ -100,7 +70,7 @@
|
||||||
currentDirection={$currentView?.sortDirection || null}
|
currentDirection={$currentView?.sortDirection || null}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button id="newButton" on:click={addCard}>New</button>
|
<button id="newButton" on:click={async () => Card.create(project)}>New</button>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|
|
@ -1,103 +1,57 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import currentView from '$lib/stores/currentView';
|
import currentView from '$lib/stores/currentView';
|
||||||
import Card, { cards } from '$lib/types/Card';
|
import { cards } from '$lib/types/Card';
|
||||||
import type Filter from '$lib/types/Filter';
|
|
||||||
import type Project from '$lib/types/Project';
|
import type Project from '$lib/types/Project';
|
||||||
import type ProjectTag from '$lib/types/ProjectTag';
|
|
||||||
import { projectTags } from '$lib/types/ProjectTag';
|
|
||||||
import type TagOption from '$lib/types/TagOption';
|
|
||||||
import type View from '$lib/types/View';
|
|
||||||
import Column from './Column.svelte';
|
import Column from './Column.svelte';
|
||||||
import Header from './Header.svelte';
|
import Header from './Header.svelte';
|
||||||
|
|
||||||
export let project: Project;
|
export let project: Project;
|
||||||
|
|
||||||
function cardComparator(a: Card, b: Card, sortTag: ProjectTag | null, sortDirection: number) {
|
$: allCards = $cards;
|
||||||
if (!sortTag) return 0;
|
|
||||||
|
|
||||||
const aTag = a.cardTags.find((t) => t.projectTag === sortTag);
|
|
||||||
const bTag = b.cardTags.find((t) => t.projectTag === sortTag);
|
|
||||||
|
|
||||||
if (!aTag) return -sortDirection;
|
|
||||||
if (!bTag) return sortDirection;
|
|
||||||
|
|
||||||
const aValue = aTag.value || aTag.option?.value || '';
|
|
||||||
const bValue = bTag.value || bTag.option?.value || '';
|
|
||||||
|
|
||||||
return aValue < bValue ? sortDirection : -sortDirection;
|
|
||||||
}
|
|
||||||
|
|
||||||
function passFilters(card: Card, filters: Filter[]): boolean {
|
|
||||||
for (const projectTag of $projectTags) {
|
|
||||||
let is: TagOption[] = [];
|
|
||||||
|
|
||||||
const cardTag = card.cardTags.find((t) => t.projectTag === projectTag);
|
|
||||||
|
|
||||||
for (const filter of filters) {
|
|
||||||
if (projectTag !== filter.projectTag) continue;
|
|
||||||
if (!filter.tagOption) continue;
|
|
||||||
|
|
||||||
if (filter.filterType === 0) {
|
|
||||||
is.push(filter.tagOption);
|
|
||||||
} else if (filter.filterType === 1) {
|
|
||||||
if (cardTag?.option === filter.tagOption) return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is.length > 0) {
|
|
||||||
if (!cardTag) return false;
|
|
||||||
if (!is.some((o) => o === cardTag?.option)) return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractColumnCards(view: View | null, cards: Card[], tagOption: TagOption | null) {
|
|
||||||
if (!view) return cards;
|
|
||||||
|
|
||||||
const filteredCards = cards.filter((c) => passFilters(c, view.filters));
|
|
||||||
|
|
||||||
const primaryTag = view.primaryTag;
|
|
||||||
|
|
||||||
if (!primaryTag) return filteredCards;
|
|
||||||
|
|
||||||
if (!tagOption) {
|
|
||||||
return filteredCards.filter((c) => !c.cardTags.map((t) => t.projectTag).includes(primaryTag));
|
|
||||||
}
|
|
||||||
|
|
||||||
const rightOptionCards = filteredCards.filter((c) => {
|
|
||||||
const tag = c.cardTags.find((t) => t.projectTag === primaryTag);
|
|
||||||
if (!tag) return false;
|
|
||||||
return tag.option?.id === tagOption.id;
|
|
||||||
});
|
|
||||||
|
|
||||||
const sortedCards = rightOptionCards.sort((a, b) =>
|
|
||||||
cardComparator(a, b, view.sortTag, $currentView?.sortDirection || 1)
|
|
||||||
);
|
|
||||||
|
|
||||||
return sortedCards;
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if project}
|
{#if project}
|
||||||
<section>
|
<section>
|
||||||
{#if $currentView}
|
{#if $currentView}
|
||||||
<Header {project} />
|
<Header {project} />
|
||||||
{#if $cards}
|
{#if $cards && allCards}
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
{#if $currentView.primaryTag}
|
{#if $currentView.primaryTag}
|
||||||
{#each $currentView.primaryTag.options as option}
|
{#each $currentView.primaryTag.options as option}
|
||||||
<Column
|
<Column
|
||||||
{option}
|
{option}
|
||||||
primaryTag={$currentView.primaryTag}
|
primaryTag={$currentView.primaryTag}
|
||||||
columnCards={extractColumnCards($currentView, $cards, option)}
|
columnCards={allCards
|
||||||
|
.filter((c) => c.cardTags.map((t) => t.option).includes(option))
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (!$currentView?.sortTag) return 0;
|
||||||
|
const aTag = a.cardTags.find((t) => t.projectTag === $currentView?.sortTag);
|
||||||
|
const bTag = b.cardTags.find((t) => t.projectTag === $currentView?.sortTag);
|
||||||
|
|
||||||
|
if (!aTag) return -($currentView?.sortDirection || 1);
|
||||||
|
if (!bTag) return $currentView?.sortDirection || 1;
|
||||||
|
|
||||||
|
const aValue = aTag.value || aTag.option?.value || '';
|
||||||
|
const bValue = bTag.value || bTag.option?.value || '';
|
||||||
|
|
||||||
|
return aValue < bValue
|
||||||
|
? $currentView?.sortDirection || 1
|
||||||
|
: -($currentView?.sortDirection || 1);
|
||||||
|
})}
|
||||||
{project}
|
{project}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
<Column
|
<Column
|
||||||
primaryTag={$currentView.primaryTag}
|
primaryTag={$currentView.primaryTag}
|
||||||
columnCards={extractColumnCards($currentView, $cards, null)}
|
columnCards={$currentView.primaryTag != null
|
||||||
|
? (() => {
|
||||||
|
const primaryTag = $currentView.primaryTag;
|
||||||
|
return allCards.filter(
|
||||||
|
(c) => !c.cardTags.map((t) => t.projectTag).includes(primaryTag)
|
||||||
|
);
|
||||||
|
})()
|
||||||
|
: allCards}
|
||||||
{project}
|
{project}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,154 +0,0 @@
|
||||||
import { writable } from 'svelte/store';
|
|
||||||
import View from './View';
|
|
||||||
import ProjectTag from './ProjectTag';
|
|
||||||
import TagOption from './TagOption';
|
|
||||||
import { toastAlert } from '$lib/utils/toasts';
|
|
||||||
import filtersApi from '$lib/api/filtersApi';
|
|
||||||
|
|
||||||
export default class Filter {
|
|
||||||
private _id: number;
|
|
||||||
private _view: View;
|
|
||||||
private _projectTag: ProjectTag;
|
|
||||||
private _filterType: number;
|
|
||||||
private _tagOption: TagOption | null;
|
|
||||||
|
|
||||||
private constructor(
|
|
||||||
id: number,
|
|
||||||
view: View,
|
|
||||||
projectTag: ProjectTag,
|
|
||||||
filterType: number,
|
|
||||||
tagOption: TagOption | null
|
|
||||||
) {
|
|
||||||
this._id = id;
|
|
||||||
this._view = view;
|
|
||||||
this._projectTag = projectTag;
|
|
||||||
this._filterType = filterType;
|
|
||||||
this._tagOption = tagOption;
|
|
||||||
}
|
|
||||||
|
|
||||||
get id(): number {
|
|
||||||
return this._id;
|
|
||||||
}
|
|
||||||
|
|
||||||
get view(): View {
|
|
||||||
return this._view;
|
|
||||||
}
|
|
||||||
|
|
||||||
get projectTag(): ProjectTag {
|
|
||||||
return this._projectTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
get filterType(): number {
|
|
||||||
return this._filterType;
|
|
||||||
}
|
|
||||||
|
|
||||||
get tagOption(): TagOption | null {
|
|
||||||
return this._tagOption;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async create(
|
|
||||||
view: View,
|
|
||||||
projectTag: ProjectTag,
|
|
||||||
filterType: number,
|
|
||||||
tagOption: TagOption | null
|
|
||||||
): Promise<Filter | null> {
|
|
||||||
const id = await filtersApi.create(view.id, projectTag.id, filterType, tagOption?.id || null);
|
|
||||||
if (!id) return null;
|
|
||||||
|
|
||||||
return new Filter(id, view, projectTag, filterType, tagOption);
|
|
||||||
}
|
|
||||||
|
|
||||||
async delete(): Promise<boolean> {
|
|
||||||
return await filtersApi.delete(this.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
async setProjectTag(projectTag: ProjectTag): Promise<boolean> {
|
|
||||||
const res = await filtersApi.update(
|
|
||||||
this.id,
|
|
||||||
this.view.id,
|
|
||||||
projectTag.id,
|
|
||||||
this.filterType,
|
|
||||||
this.tagOption?.id || null
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!res) return false;
|
|
||||||
|
|
||||||
this._projectTag = projectTag;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async setFilterType(filterType: number): Promise<boolean> {
|
|
||||||
const res = await filtersApi.update(
|
|
||||||
this.id,
|
|
||||||
this.view.id,
|
|
||||||
this.projectTag.id,
|
|
||||||
filterType,
|
|
||||||
this.tagOption?.id || null
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!res) return false;
|
|
||||||
|
|
||||||
this._filterType = filterType;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async setTagOption(tagOption: TagOption | null): Promise<boolean> {
|
|
||||||
const res = await filtersApi.update(
|
|
||||||
this.id,
|
|
||||||
this.view.id,
|
|
||||||
this.projectTag.id,
|
|
||||||
this.filterType,
|
|
||||||
tagOption?.id || null
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!res) return false;
|
|
||||||
|
|
||||||
this._tagOption = tagOption;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static parseAll(json: any): Filter[];
|
|
||||||
static parseAll(json: any, view: View | null): Filter[];
|
|
||||||
|
|
||||||
static parseAll(json: any[], view?: View | null): Filter[] {
|
|
||||||
if (!json) return [];
|
|
||||||
|
|
||||||
const filters: Filter[] = [];
|
|
||||||
|
|
||||||
for (const filter of json) {
|
|
||||||
const parsed = Filter.parse(filter, view);
|
|
||||||
if (parsed) filters.push(parsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
return filters;
|
|
||||||
}
|
|
||||||
|
|
||||||
static parse(json: any): Filter | null;
|
|
||||||
static parse(json: any, view: View | null | undefined): Filter | null;
|
|
||||||
|
|
||||||
static parse(json: any, view?: View | null | undefined): Filter | null {
|
|
||||||
if (!json) {
|
|
||||||
toastAlert('Failed to parse filter: json is null');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!view) view = View.fromId(json.view_id);
|
|
||||||
if (!view) {
|
|
||||||
toastAlert('Failed to parse filter: view not found');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const projectTag = ProjectTag.fromId(json.tag_id);
|
|
||||||
if (!projectTag) {
|
|
||||||
toastAlert('Failed to parse filter: projectTag not found');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tagOption = projectTag.options.find((option) => option.id === json.option_id);
|
|
||||||
|
|
||||||
return new Filter(json.id, view, projectTag, json.filter_type, tagOption || null);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +1,8 @@
|
||||||
import { get, writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
import Project from './Project';
|
import Project from './Project';
|
||||||
import ProjectTag from './ProjectTag';
|
import ProjectTag from './ProjectTag';
|
||||||
import viewsApi from '$lib/api/viewsApi';
|
import viewsApi from '$lib/api/viewsApi';
|
||||||
import { toastAlert } from '$lib/utils/toasts';
|
import { toastAlert } from '$lib/utils/toasts';
|
||||||
import Filter from './Filter';
|
|
||||||
import type TagOption from './TagOption';
|
|
||||||
|
|
||||||
export const views = writable([] as View[]);
|
export const views = writable([] as View[]);
|
||||||
|
|
||||||
|
@ -16,7 +14,6 @@ export default class View {
|
||||||
private _title: string;
|
private _title: string;
|
||||||
private _sortTag: ProjectTag | null;
|
private _sortTag: ProjectTag | null;
|
||||||
private _sortDirection: number | null;
|
private _sortDirection: number | null;
|
||||||
private _filters: Filter[];
|
|
||||||
|
|
||||||
private constructor(
|
private constructor(
|
||||||
id: number,
|
id: number,
|
||||||
|
@ -25,8 +22,7 @@ export default class View {
|
||||||
secondaryTag: ProjectTag | null,
|
secondaryTag: ProjectTag | null,
|
||||||
title: string,
|
title: string,
|
||||||
sortTag: ProjectTag | null,
|
sortTag: ProjectTag | null,
|
||||||
sortDirection: number | null,
|
sortDirection: number | null
|
||||||
filters: Filter[]
|
|
||||||
) {
|
) {
|
||||||
this._id = id;
|
this._id = id;
|
||||||
this._project = project;
|
this._project = project;
|
||||||
|
@ -35,7 +31,6 @@ export default class View {
|
||||||
this._title = title;
|
this._title = title;
|
||||||
this._sortTag = sortTag;
|
this._sortTag = sortTag;
|
||||||
this._sortDirection = sortDirection;
|
this._sortDirection = sortDirection;
|
||||||
this._filters = filters;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get id(): number {
|
get id(): number {
|
||||||
|
@ -66,20 +61,6 @@ export default class View {
|
||||||
return this._sortDirection;
|
return this._sortDirection;
|
||||||
}
|
}
|
||||||
|
|
||||||
get filters(): Filter[] {
|
|
||||||
return this._filters;
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromId(id: number): View | null {
|
|
||||||
for (const view of get(views)) {
|
|
||||||
if (view.id === id) {
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async setPrimaryTag(projectTag: ProjectTag): Promise<boolean> {
|
async setPrimaryTag(projectTag: ProjectTag): Promise<boolean> {
|
||||||
const response = await viewsApi.update(
|
const response = await viewsApi.update(
|
||||||
this.id,
|
this.id,
|
||||||
|
@ -122,7 +103,7 @@ export default class View {
|
||||||
|
|
||||||
if (!id) return null;
|
if (!id) return null;
|
||||||
|
|
||||||
const view = new View(id, project, null, null, 'New view', null, null, []);
|
const view = new View(id, project, null, null, 'New view', null, null);
|
||||||
|
|
||||||
views.update((views) => [...views, view]);
|
views.update((views) => [...views, view]);
|
||||||
|
|
||||||
|
@ -156,27 +137,6 @@ export default class View {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async addFilter(
|
|
||||||
projectTag: ProjectTag,
|
|
||||||
filterType: number,
|
|
||||||
option: TagOption | null
|
|
||||||
): Promise<Filter | null> {
|
|
||||||
const filter = await Filter.create(this, projectTag, filterType, option);
|
|
||||||
if (!filter) return null;
|
|
||||||
|
|
||||||
this._filters = [...this._filters, filter];
|
|
||||||
|
|
||||||
return filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
async removeFilter(filter: Filter): Promise<boolean> {
|
|
||||||
if (!(await filter.delete())) return false;
|
|
||||||
|
|
||||||
this._filters = this._filters.filter((f) => f.id !== filter.id);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static parse(json: any): View | null;
|
static parse(json: any): View | null;
|
||||||
static parse(json: any, project: Project | null | undefined): View | null;
|
static parse(json: any, project: Project | null | undefined): View | null;
|
||||||
|
|
||||||
|
@ -203,12 +163,9 @@ export default class View {
|
||||||
secondaryTag,
|
secondaryTag,
|
||||||
json.title,
|
json.title,
|
||||||
sortTag,
|
sortTag,
|
||||||
json.sort_direction,
|
json.sort_direction
|
||||||
[]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
view._filters = Filter.parseAll(json.filters, view);
|
|
||||||
|
|
||||||
views.update((views) => {
|
views.update((views) => {
|
||||||
if (!views.find((view) => view.id === json.id)) {
|
if (!views.find((view) => view.id === json.id)) {
|
||||||
return [...views, view];
|
return [...views, view];
|
||||||
|
|
Loading…
Reference in New Issue