Update Design
This commit is contained in:
parent
cc9ddc21a0
commit
e8ec7919d9
|
@ -242,7 +242,7 @@ func UpdateTagOption(c *fiber.Ctx) error {
|
|||
return c.SendStatus(fiber.StatusNotFound)
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusOK)
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
}
|
||||
|
||||
func DeleteTagOptions(c *fiber.Ctx) error {
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"axios": "^1.6.3",
|
||||
"less": "^4.2.0",
|
||||
"svelte-multiselect": "^10.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -1432,6 +1433,17 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/copy-anything": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz",
|
||||
"integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==",
|
||||
"dependencies": {
|
||||
"is-what": "^3.14.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/mesqueeb"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
|
@ -1557,6 +1569,18 @@
|
|||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/errno": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz",
|
||||
"integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"prr": "~1.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"errno": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/es6-promise": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
|
||||
|
@ -2122,7 +2146,7 @@
|
|||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/graphemer": {
|
||||
"version": "1.4.0",
|
||||
|
@ -2139,6 +2163,18 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz",
|
||||
|
@ -2148,6 +2184,18 @@
|
|||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/image-size": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
|
||||
"integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"image-size": "bin/image-size.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/import-fresh": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||
|
@ -2258,6 +2306,11 @@
|
|||
"@types/estree": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/is-what": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz",
|
||||
"integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA=="
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
|
@ -2318,6 +2371,31 @@
|
|||
"integrity": "sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/less": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz",
|
||||
"integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==",
|
||||
"dependencies": {
|
||||
"copy-anything": "^2.0.1",
|
||||
"parse-node-version": "^1.0.1",
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"bin": {
|
||||
"lessc": "bin/lessc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"errno": "^0.1.1",
|
||||
"graceful-fs": "^4.1.2",
|
||||
"image-size": "~0.5.0",
|
||||
"make-dir": "^2.1.0",
|
||||
"mime": "^1.4.1",
|
||||
"needle": "^3.1.0",
|
||||
"source-map": "~0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/levn": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
||||
|
@ -2389,6 +2467,28 @@
|
|||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
||||
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"pify": "^4.0.1",
|
||||
"semver": "^5.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir/node_modules/semver": {
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.0.30",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
|
||||
|
@ -2416,6 +2516,18 @@
|
|||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
|
@ -2528,6 +2640,22 @@
|
|||
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/needle": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz",
|
||||
"integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"iconv-lite": "^0.6.3",
|
||||
"sax": "^1.2.4"
|
||||
},
|
||||
"bin": {
|
||||
"needle": "bin/needle"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4.4.x"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
|
@ -2605,6 +2733,14 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-node-version": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz",
|
||||
"integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==",
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
|
@ -2669,6 +2805,15 @@
|
|||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pify": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
|
||||
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.40.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.1.tgz",
|
||||
|
@ -2850,6 +2995,12 @@
|
|||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/prr": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
|
||||
"integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
|
@ -2989,6 +3140,12 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/sander": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz",
|
||||
|
@ -3013,6 +3170,12 @@
|
|||
"rimraf": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz",
|
||||
"integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
|
@ -3093,6 +3256,15 @@
|
|||
"sorcery": "bin/sorcery"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||
|
@ -3356,8 +3528,7 @@
|
|||
"node_modules/tslib": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
"type": "module",
|
||||
"dependencies": {
|
||||
"axios": "^1.6.3",
|
||||
"less": "^4.2.0",
|
||||
"svelte-multiselect": "^10.2.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import type { Card } from '../stores/interfaces';
|
||||
import type { Card, TagValue } from '../stores/interfaces';
|
||||
import api, { processError } from '../utils/api';
|
||||
import status from '../utils/status';
|
||||
|
||||
export async function newCardApi(projectId: number): Promise<Card> {
|
||||
export async function newCardApi(projectId: number, tags: TagValue[]): Promise<Card> {
|
||||
const response = await api.post(`/v1/cards`, {
|
||||
project_id: projectId,
|
||||
title: 'Untitled',
|
||||
|
@ -16,12 +16,14 @@ export async function newCardApi(projectId: number): Promise<Card> {
|
|||
|
||||
const id: number = response.data.id;
|
||||
|
||||
tags.forEach((tag) => (tag.card_id = id));
|
||||
|
||||
return {
|
||||
id: id,
|
||||
projectId: projectId,
|
||||
title: 'Untitled',
|
||||
content: '',
|
||||
tags: []
|
||||
tags: tags
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import type { MeTag, TagOption } from '../stores/interfaces';
|
||||
import api, { processError } from '../utils/api';
|
||||
import status from '../utils/status';
|
||||
|
||||
export async function updateTagAPI(option: TagOption): Promise<boolean> {
|
||||
const response =
|
||||
option.value === ''
|
||||
? await api.delete(`/v1/tags/${option.tag_id}/options/${option.id}`)
|
||||
: await api.put(`/v1/tags/${option.tag_id}/options/${option.id}`, option);
|
||||
|
||||
if (response.status !== status.NoContent) {
|
||||
processError(response, 'Failed to update tag option');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="/img/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="stylesheet" type="text/css" href="/css/index.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/css/global.css" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { updateCardTagApi } from '../api/cards';
|
||||
import type { Card, TagOption } from '../stores/interfaces';
|
||||
import { cards, currentDraggedCard } from '../stores/smallStore';
|
||||
import api, { processError } from '../utils/api';
|
||||
import status from '../utils/status';
|
||||
import CardC from './card.svelte';
|
||||
|
||||
export let option: TagOption;
|
||||
export let columnCards: Card[] = [];
|
||||
|
||||
async function onDrop(e: DragEvent) {
|
||||
e.preventDefault();
|
||||
if ($currentDraggedCard && $currentDraggedCard.tags) {
|
||||
for (let tag of $currentDraggedCard.tags) {
|
||||
if (tag.tag_id == option.tag_id) {
|
||||
try {
|
||||
if (tag.option_id == option.id) return;
|
||||
// DELETE
|
||||
if (tag.option_id !== -1 && option.id === -1) {
|
||||
const response = await api.delete(`/v1/cards/${tag.card_id}/tags/${tag.tag_id}`);
|
||||
|
||||
if (response.status !== status.NoContent) {
|
||||
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();
|
||||
} catch (e) {}
|
||||
break;
|
||||
}
|
||||
}
|
||||
currentDraggedCard.set(null);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="column"
|
||||
role="listbox"
|
||||
tabindex="-1"
|
||||
on:drop={onDrop}
|
||||
on:dragover={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<h3>{option.value}</h3>
|
||||
<ul>
|
||||
{#each columnCards as card}
|
||||
<CardC {card} />
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
h3 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.column {
|
||||
margin: 0 10px;
|
||||
width: 200px;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,13 @@
|
|||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="white"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<line x1="12" y1="7" x2="12" y2="21"></line>
|
||||
<line x1="5" y1="14" x2="19" y2="14"></line>
|
||||
</svg>
|
After Width: | Height: | Size: 271 B |
|
@ -0,0 +1,7 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 512 512"
|
||||
><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path
|
||||
opacity="1"
|
||||
fill="#FFFFFF"
|
||||
d="M128 32h32c17.7 0 32 14.3 32 32V96H96V64c0-17.7 14.3-32 32-32zm64 96V448c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32V388.9c0-34.6 9.4-68.6 27.2-98.3C40.9 267.8 49.7 242.4 53 216L60.5 156c2-16 15.6-28 31.8-28H192zm227.8 0c16.1 0 29.8 12 31.8 28L459 216c3.3 26.4 12.1 51.8 25.8 74.6c17.8 29.7 27.2 63.7 27.2 98.3V448c0 17.7-14.3 32-32 32H352c-17.7 0-32-14.3-32-32V128h99.8zM320 64c0-17.7 14.3-32 32-32h32c17.7 0 32 14.3 32 32V96H320V64zm-32 64V288H224V128h64z"
|
||||
/></svg
|
||||
>
|
After Width: | Height: | Size: 752 B |
|
@ -1,65 +0,0 @@
|
|||
<script lang="ts">
|
||||
import ModalTags from './modal_tags.svelte';
|
||||
import type { Card } from '../stores/interfaces';
|
||||
import api, { processError } from '../utils/api';
|
||||
import status from '../utils/status';
|
||||
import { cards, currentModalCard } from '../stores/smallStore';
|
||||
import TrashIcon from './icons/trashIcon.svelte';
|
||||
import CloseIcon from './icons/closeIcon.svelte';
|
||||
|
||||
export let card: Card;
|
||||
|
||||
let tempCard: Card = { ...card };
|
||||
|
||||
async function save(closeModal: boolean = true) {
|
||||
if (
|
||||
card.projectId != tempCard.projectId ||
|
||||
card.title !== tempCard.title ||
|
||||
card.content !== tempCard.content
|
||||
) {
|
||||
const response = await api.put(`/v1/cards/${card.id}`, {
|
||||
project_id: tempCard.projectId,
|
||||
title: tempCard.title,
|
||||
content: tempCard.content
|
||||
});
|
||||
|
||||
if (response.status !== status.NoContent) {
|
||||
processError(response, 'Failed to update card');
|
||||
return;
|
||||
}
|
||||
|
||||
card = { ...tempCard };
|
||||
}
|
||||
if (closeModal) currentModalCard.set(-1);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $currentModalCard == card.id}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="modal" on:click={() => save(true)}>
|
||||
<div class="content" on:click|stopPropagation>
|
||||
<div class="header">
|
||||
<input class="title" bind:value={tempCard.title} on:blur={() => save(false)} />
|
||||
<div class="buttons">
|
||||
<button on:click={() => cards.remove(card)}>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
<button on:click={() => currentModalCard.set(-1)}>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tags">
|
||||
<ModalTags bind:card />
|
||||
</div>
|
||||
<div class="body">
|
||||
<textarea
|
||||
bind:value={tempCard.content}
|
||||
placeholder="Add a description"
|
||||
on:blur={() => save(false)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
|
@ -1,108 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { type Project, type Card, type TagOption, type View } from '../stores/interfaces';
|
||||
import projectTags from '../stores/projectTags';
|
||||
import { getProjectAPI } from '../api/projects';
|
||||
import Column from './column.svelte';
|
||||
import { cards, currentView } from '../stores/smallStore';
|
||||
|
||||
export let projectId: number;
|
||||
|
||||
let project: Project;
|
||||
// let cards: Card[] = [];
|
||||
let view: View | null = null;
|
||||
let columns: { id: number; title: string; cards: Card[] }[] = [];
|
||||
|
||||
onMount(async () => {
|
||||
getProjectAPI(projectId).then((p) => {
|
||||
project = p;
|
||||
});
|
||||
|
||||
cards.init(projectId);
|
||||
|
||||
cards.subscribe((c) => {
|
||||
loadColumns();
|
||||
});
|
||||
|
||||
if (!(await projectTags.init(projectId))) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentView.subscribe((v) => {
|
||||
view = v;
|
||||
loadColumns();
|
||||
});
|
||||
});
|
||||
|
||||
function loadColumns() {
|
||||
if (!view) return;
|
||||
let primary_tag_id = view.primary_tag_id;
|
||||
columns =
|
||||
$projectTags[primary_tag_id]?.options.map((o) => {
|
||||
return {
|
||||
id: o.id,
|
||||
title: o.value,
|
||||
cards: $cards?.filter((c) => c.tags.map((t) => t.option_id).includes(o.id)) || []
|
||||
};
|
||||
}) || [];
|
||||
columns.push({
|
||||
id: -1,
|
||||
title: 'No tag',
|
||||
cards:
|
||||
$cards?.filter((c) => {
|
||||
const tag = c.tags.find((t) => t.tag_id === primary_tag_id);
|
||||
return tag?.option_id == -1;
|
||||
}) || []
|
||||
});
|
||||
}
|
||||
|
||||
async function newCard() {
|
||||
await cards.add(projectId);
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<link rel="stylesheet" type="text/css" href="/css/project.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/css/card.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/css/modalCard.css" />
|
||||
</svelte:head>
|
||||
|
||||
{#if project}
|
||||
<section>
|
||||
<header>
|
||||
<h2>{project.title}</h2>
|
||||
<button on:click={newCard}>New card</button>
|
||||
</header>
|
||||
{#if view && $projectTags[view.primary_tag_id] && $cards}
|
||||
<div class="grid">
|
||||
{#each $projectTags[view.primary_tag_id].options as option}
|
||||
<Column
|
||||
{option}
|
||||
columnCards={$cards.filter((c) => c.tags.map((t) => t.option_id).includes(option.id))}
|
||||
/>
|
||||
{/each}
|
||||
<Column
|
||||
option={{
|
||||
id: -1,
|
||||
tag_id: view.primary_tag_id,
|
||||
value: `No ${$projectTags[view.primary_tag_id].title}`
|
||||
}}
|
||||
columnCards={$cards.filter((c) => c.tags.find((t) => t.tag_id)?.option_id == -1 || false)}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import type { Card } from '../stores/interfaces';
|
||||
import projectTags from '../stores/projectTags';
|
||||
import { currentDraggedCard, currentModalCard } from '../stores/smallStore';
|
||||
import type { Card } from '../../../stores/interfaces';
|
||||
import projectTags from '../../../stores/projectTags';
|
||||
import { currentModalCard, currentDraggedCard } from '../../../stores/smallStore';
|
||||
import ModalCard from './modal_card.svelte';
|
||||
|
||||
export let card: Card;
|
||||
|
@ -40,3 +40,36 @@
|
|||
</div>
|
||||
|
||||
<ModalCard bind:card />
|
||||
|
||||
<style lang="less">
|
||||
.card {
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #555;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-size: 14px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
background-color: #303030;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card .title {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.card .tags {
|
||||
padding-top: 10px;
|
||||
font-weight: lighter;
|
||||
}
|
||||
|
||||
.card .tag {
|
||||
padding: 2px 8px;
|
||||
margin: 4px 4px 0 0;
|
||||
text-transform: uppercase;
|
||||
border-radius: 3px;
|
||||
font-size: 90%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,171 @@
|
|||
<script lang="ts">
|
||||
import type { Card } from '../../../stores/interfaces';
|
||||
import { currentModalCard, cards } from '../../../stores/smallStore';
|
||||
import api, { processError } from '../../../utils/api';
|
||||
import status from '../../../utils/status';
|
||||
import CloseIcon from '../../icons/closeIcon.svelte';
|
||||
import TrashIcon from '../../icons/trashIcon.svelte';
|
||||
import ModalTags from './modal_tags.svelte';
|
||||
|
||||
export let card: Card;
|
||||
|
||||
let tempCard: Card = { ...card };
|
||||
|
||||
async function save(closeModal: boolean = true) {
|
||||
if (
|
||||
card.projectId != tempCard.projectId ||
|
||||
card.title !== tempCard.title ||
|
||||
card.content !== tempCard.content
|
||||
) {
|
||||
const response = await api.put(`/v1/cards/${card.id}`, {
|
||||
project_id: tempCard.projectId,
|
||||
title: tempCard.title,
|
||||
content: tempCard.content
|
||||
});
|
||||
|
||||
if (response.status !== status.NoContent) {
|
||||
processError(response, 'Failed to update card');
|
||||
return;
|
||||
}
|
||||
|
||||
card = { ...tempCard };
|
||||
}
|
||||
if (closeModal) currentModalCard.set(-1);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $currentModalCard == card.id}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="modal" on:click={() => save(true)}>
|
||||
<div class="content" on:click|stopPropagation>
|
||||
<div class="header">
|
||||
<input class="title" bind:value={tempCard.title} on:blur={() => save(false)} />
|
||||
<div class="buttons">
|
||||
<button on:click={() => cards.remove(card)}>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
<button on:click={() => currentModalCard.set(-1)}>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tags">
|
||||
<ModalTags bind:card />
|
||||
</div>
|
||||
<div class="body">
|
||||
<textarea
|
||||
bind:value={tempCard.content}
|
||||
placeholder="Add a description"
|
||||
on:blur={() => save(false)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="less">
|
||||
.content {
|
||||
}
|
||||
|
||||
.modal {
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.content {
|
||||
background: #1e1e1e;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal input,
|
||||
.modal textarea {
|
||||
background: none;
|
||||
color: inherit;
|
||||
border: 1px solid #333;
|
||||
border-radius: 7px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.modal .title {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal .header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.modal .buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal button {
|
||||
margin-left: 5px;
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.modal button:hover {
|
||||
background-color: #333;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal .buttons button:first-child:hover {
|
||||
background-color: #343;
|
||||
}
|
||||
|
||||
.modal .buttons button:last-child:hover {
|
||||
background-color: #433;
|
||||
}
|
||||
|
||||
.modal .body {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.modal textarea {
|
||||
width: 100%;
|
||||
min-height: 200px;
|
||||
resize: vertical;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.modal td {
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
.modal td:first-child {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.modal td:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.modal td button {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
</style>
|
|
@ -1,11 +1,12 @@
|
|||
<script lang="ts">
|
||||
import type { TagValue } from '../stores/interfaces';
|
||||
import projectTags from '../stores/projectTags';
|
||||
import api, { processError } from '../utils/api';
|
||||
import status from '../utils/status';
|
||||
import type { TagValue } from '../../../stores/interfaces';
|
||||
import projectTags from '../../../stores/projectTags';
|
||||
import { cards } from '../../../stores/smallStore';
|
||||
import api, { processError } from '../../../utils/api';
|
||||
import status from '../../../utils/status';
|
||||
|
||||
export let tag: TagValue;
|
||||
export let removeTag: (id: number) => void;
|
||||
// export let removeTag: (id: number) => void;
|
||||
let newValue: string = tag.value;
|
||||
let newOption: number = tag.option_id;
|
||||
|
||||
|
@ -47,6 +48,7 @@
|
|||
|
||||
tag.value = newValue;
|
||||
tag.option_id = newOption;
|
||||
cards.reload();
|
||||
}
|
||||
|
||||
async function newTagOption() {
|
|
@ -1,8 +1,8 @@
|
|||
<script lang="ts">
|
||||
import type { Card } from '../../../stores/interfaces';
|
||||
import api, { processError } from '../../../utils/api';
|
||||
import status from '../../../utils/status';
|
||||
import ModalTag from './modal_tag.svelte';
|
||||
import status from '../utils/status';
|
||||
import type { Card } from '../stores/interfaces';
|
||||
import api, { processError } from '../utils/api';
|
||||
|
||||
export let card: Card;
|
||||
|
||||
|
@ -41,7 +41,7 @@
|
|||
<table>
|
||||
{#if card.tags}
|
||||
{#each card.tags as tag}
|
||||
<ModalTag bind:tag {removeTag} />
|
||||
<ModalTag bind:tag />
|
||||
{/each}
|
||||
{/if}
|
||||
<tr class="tag">
|
|
@ -0,0 +1,199 @@
|
|||
<script lang="ts">
|
||||
import type { TagOption, Card, MeTag, TagValue } from '../../stores/interfaces';
|
||||
import { cards, currentDraggedCard } from '../../stores/smallStore';
|
||||
import api, { processError } from '../../utils/api';
|
||||
import status from '../../utils/status';
|
||||
import CardC from './card/card.svelte';
|
||||
import AddIcon from '../icons/addIcon.svelte';
|
||||
import projectTags from '../../stores/projectTags';
|
||||
import { updateTagAPI as updateTagOptionAPI } from '../../api/tags';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
export let projectId: number;
|
||||
export let editable: boolean = true;
|
||||
export let option: TagOption;
|
||||
export let columnCards: Card[] = [];
|
||||
|
||||
let lastOptionValue = option.value;
|
||||
|
||||
async function onDrop(e: DragEvent) {
|
||||
e.preventDefault();
|
||||
if ($currentDraggedCard && $currentDraggedCard.tags) {
|
||||
for (let tag of $currentDraggedCard.tags) {
|
||||
if (tag.tag_id == option.tag_id) {
|
||||
try {
|
||||
if (tag.option_id == option.id) return;
|
||||
// DELETE
|
||||
if (tag.option_id !== -1 && option.id === -1) {
|
||||
const response = await api.delete(`/v1/cards/${tag.card_id}/tags/${tag.tag_id}`);
|
||||
|
||||
if (response.status !== status.NoContent) {
|
||||
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();
|
||||
} catch (e) {}
|
||||
break;
|
||||
}
|
||||
}
|
||||
currentDraggedCard.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
async function addCard() {
|
||||
const tags: TagValue[] = [];
|
||||
for (let tag of Object.values(get(projectTags))) {
|
||||
tags.push({
|
||||
card_id: -1,
|
||||
tag_id: tag.id,
|
||||
option_id: tag.id === option.tag_id ? option.id : -1,
|
||||
value: ''
|
||||
});
|
||||
}
|
||||
|
||||
await cards.add(projectId, tags);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="column"
|
||||
role="listbox"
|
||||
tabindex="-1"
|
||||
on:drop={onDrop}
|
||||
on:dragover={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<header>
|
||||
<input
|
||||
bind:value={option.value}
|
||||
type="text"
|
||||
on:blur={async () => {
|
||||
if (lastOptionValue === option.value) return;
|
||||
await updateTagOptionAPI(option);
|
||||
lastOptionValue = option.value;
|
||||
cards.reload();
|
||||
}}
|
||||
disabled={!editable}
|
||||
/>
|
||||
<span>
|
||||
<span>{columnCards.length}</span>
|
||||
<span
|
||||
class="add"
|
||||
on:click={addCard}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
on:keypress={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
addCard();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<AddIcon />
|
||||
</span>
|
||||
</span>
|
||||
</header>
|
||||
<ul>
|
||||
{#each columnCards as card}
|
||||
<CardC {card} />
|
||||
{/each}
|
||||
</ul>
|
||||
<div
|
||||
class="addEnd"
|
||||
on:click={addCard}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
on:keypress={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
addCard();
|
||||
}
|
||||
}}
|
||||
>
|
||||
+
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="less">
|
||||
.column {
|
||||
margin: 20px 10px;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
header {
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
input {
|
||||
background-color: #444;
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
padding: 3px 10px;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
color: inherit;
|
||||
border: none;
|
||||
}
|
||||
|
||||
:last-child {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.add {
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-left: 10px;
|
||||
|
||||
&:hover {
|
||||
background-color: #fff1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.addEnd {
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
height: 40px;
|
||||
font-size: 150%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
&:hover {
|
||||
background-color: #fff1;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,76 @@
|
|||
<script lang="ts">
|
||||
import { get } from 'svelte/store';
|
||||
import type { Project, TagValue } from '../../stores/interfaces';
|
||||
import { cards } from '../../stores/smallStore';
|
||||
import projectTags from '../../stores/projectTags';
|
||||
|
||||
export let project: Project;
|
||||
|
||||
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;
|
||||
}
|
||||
</script>
|
||||
|
||||
<header>
|
||||
<h2>{project.title}</h2>
|
||||
<nav>
|
||||
<span>Group</span>
|
||||
<span>Sub-group</span>
|
||||
<span>Filter</span>
|
||||
<span>Sort</span>
|
||||
<button on:click={async () => cards.add(project.id, getEmptyTags())}>New</button>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<style lang="less">
|
||||
header {
|
||||
padding: 20px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 2px solid #444;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
nav {
|
||||
* {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-right: 10px;
|
||||
color: #aaa;
|
||||
padding: 5px 10px;
|
||||
border-radius: 7px;
|
||||
|
||||
&:hover {
|
||||
// background-color: #fff2;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
background: #324067;
|
||||
color: inherit;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
padding: 10px 20px;
|
||||
font-size: inherit;
|
||||
|
||||
&:hover {
|
||||
// background-color: #3a4a77;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,66 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import Column from './column.svelte';
|
||||
import type { Project, View } from '../../stores/interfaces';
|
||||
import projectTags from '../../stores/projectTags';
|
||||
import { cards, currentView } from '../../stores/smallStore';
|
||||
import Header from './header.svelte';
|
||||
|
||||
export let project: Project;
|
||||
|
||||
let view: View | null = null;
|
||||
|
||||
onMount(async () => {
|
||||
await cards.init(project.id);
|
||||
|
||||
if (!(await projectTags.init(project.id))) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentView.subscribe((v) => {
|
||||
view = v;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if project}
|
||||
<section>
|
||||
<Header {project} />
|
||||
{#if view && $projectTags[view.primary_tag_id] && $cards}
|
||||
<div class="grid">
|
||||
{#each $projectTags[view.primary_tag_id].options as option}
|
||||
<Column
|
||||
{option}
|
||||
columnCards={$cards.filter((c) => c.tags.map((t) => t.option_id).includes(option.id))}
|
||||
projectId={project.id}
|
||||
/>
|
||||
{/each}
|
||||
<Column
|
||||
option={{
|
||||
id: -1,
|
||||
tag_id: view.primary_tag_id,
|
||||
value: `No ${$projectTags[view.primary_tag_id].title}`
|
||||
}}
|
||||
columnCards={$cards.filter((c) => c.tags.find((t) => t.tag_id)?.option_id == -1 || false)}
|
||||
projectId={project.id}
|
||||
editable={false}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
margin: 0 40px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import type { Project } from '../stores/interfaces';
|
||||
import api, { processError } from '../utils/api';
|
||||
import status from '../utils/status';
|
||||
import type { Project } from '../../stores/interfaces';
|
||||
import api, { processError } from '../../utils/api';
|
||||
import status from '../../utils/status';
|
||||
|
||||
export let project: Project;
|
||||
export let deleteProject: (project: Project) => void;
|
||||
|
@ -94,3 +94,50 @@
|
|||
/>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<style lang="less">
|
||||
li {
|
||||
cursor: pointer;
|
||||
margin: 10px 0;
|
||||
border: 1px solid #555;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.title:hover {
|
||||
background-color: #303030;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
li img {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
li img:hover {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
background-color: #333;
|
||||
color: inherit;
|
||||
font-weight: bold;
|
||||
font-size: inherit;
|
||||
}
|
||||
</style>
|
|
@ -1,14 +1,15 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import api, { processError } from '../utils/api';
|
||||
import type { View } from '../stores/interfaces';
|
||||
import type { Project, View } from '../stores/interfaces';
|
||||
import { currentView } from '../stores/smallStore';
|
||||
import ViewIcon from './icons/viewIcon.svelte';
|
||||
|
||||
export let projectID: number;
|
||||
export let project: Project;
|
||||
let views: View[];
|
||||
|
||||
onMount(async () => {
|
||||
const response = await api.get(`/v1/projects/${projectID}/views`);
|
||||
const response = await api.get(`/v1/projects/${project.id}/views`);
|
||||
|
||||
if (response.status !== 200) {
|
||||
processError(response, 'Failed to fetch views');
|
||||
|
@ -21,30 +22,95 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/css/sidebar.css" />
|
||||
<nav>
|
||||
<div>
|
||||
<div id="branding">
|
||||
<span id="title">Focus.</span>
|
||||
<span id="version">v0.0.1</span>
|
||||
</div>
|
||||
<div id="views">
|
||||
{#if views}
|
||||
<h2>{project.title}</h2>
|
||||
<ul>
|
||||
{#each views as view}
|
||||
<li>
|
||||
<ViewIcon />
|
||||
<!-- on:click={() => {
|
||||
currentView.set(view);
|
||||
}} -->
|
||||
<span>
|
||||
{view.title}
|
||||
</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="separator"></div>
|
||||
<div id="newView" on:click={() => {}}>+ New view</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="sidebar" class="sidebar">
|
||||
<div class="logo">
|
||||
<a href="/">
|
||||
<img src="img/icon.svg" alt="" />
|
||||
<span class="title">Focus</span>
|
||||
<span class="version">v0.0.1</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="boards">
|
||||
<h2>Projects</h2>
|
||||
{#if views}
|
||||
<ul>
|
||||
{#each views as view}
|
||||
<li>
|
||||
<span
|
||||
on:click={() => {
|
||||
currentView.set(view);
|
||||
}}>{view.title}</span
|
||||
>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<style lang="less">
|
||||
nav {
|
||||
min-width: 300px;
|
||||
background-color: #273049;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
#branding {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20px;
|
||||
|
||||
#title {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
#version {
|
||||
font-size: 30px;
|
||||
color: #aaa;
|
||||
}
|
||||
}
|
||||
|
||||
#views {
|
||||
h2 {
|
||||
font-weight: normal;
|
||||
font-size: 25px;
|
||||
padding: 20px 10px;
|
||||
}
|
||||
|
||||
ul {
|
||||
font-size: 22px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
span {
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.separator {
|
||||
height: 2px;
|
||||
widows: 100%;
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
#newView {
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
#newView:hover {
|
||||
cursor: pointer;
|
||||
background-color: #fff1;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import type { Project } from '../stores/interfaces';
|
||||
import { onMount } from 'svelte';
|
||||
import api, { processError } from '../utils/api';
|
||||
import SelectProject from '../components/selectProject.svelte';
|
||||
import SelectProject from '../components/projects/selectProject.svelte';
|
||||
|
||||
let projects: Project[];
|
||||
|
||||
|
@ -46,11 +46,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<link rel="stylesheet" type="text/css" href="/css/projects.css" />
|
||||
</svelte:head>
|
||||
|
||||
<section id="projects">
|
||||
<section>
|
||||
<h2>Projects</h2>
|
||||
<ul>
|
||||
{#if projects}
|
||||
|
@ -75,3 +71,30 @@
|
|||
</section>
|
||||
|
||||
<SvelteToast />
|
||||
|
||||
<style lang="less">
|
||||
section {
|
||||
margin: 40px;
|
||||
}
|
||||
h2 {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
#add {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 20px 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#add:hover {
|
||||
background-color: #303030;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,23 +1,33 @@
|
|||
<script lang="ts">
|
||||
import Project from '../../components/project.svelte';
|
||||
import Sidebar from '../../components/sidebar.svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { SvelteToast } from '@zerodevx/svelte-toast';
|
||||
import type { View } from '../../stores/interfaces';
|
||||
import { onMount } from 'svelte';
|
||||
import { getProjectAPI } from '../../api/projects';
|
||||
import { type Project as P } from '../../stores/interfaces';
|
||||
import Sidebar from '../../components/sidebar.svelte';
|
||||
import Project from '../../components/project/project.svelte';
|
||||
|
||||
let projectID: number = +$page.params.project;
|
||||
let projectId: number = +$page.params.project;
|
||||
|
||||
let currentView: View;
|
||||
let project: P;
|
||||
|
||||
onMount(() => {
|
||||
getProjectAPI(projectId).then((p) => {
|
||||
project = p;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="projectPage">
|
||||
<Sidebar {projectID} />
|
||||
<Project projectId={projectID} />
|
||||
</div>
|
||||
<SvelteToast />
|
||||
{#if project}
|
||||
<div>
|
||||
<Sidebar {project} />
|
||||
<Project {project} />
|
||||
</div>
|
||||
<SvelteToast />
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
#projectPage {
|
||||
div {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { writable } from 'svelte/store';
|
||||
import { parseCards, type Card, type View } from './interfaces';
|
||||
import { parseCards, type Card, type View, type TagValue } from './interfaces';
|
||||
import { deleteCardApi, newCardApi } from '../api/cards';
|
||||
import { getProjectCardsAPI } from '../api/projects';
|
||||
|
||||
|
@ -19,10 +19,10 @@ export const cards = (() => {
|
|||
set(parseCards(c));
|
||||
});
|
||||
},
|
||||
add: async (projectId: number) => {
|
||||
await newCardApi(projectId).then((card) => {
|
||||
currentModalCard.set(card.id);
|
||||
add: async (projectId: number, tags: TagValue[]) => {
|
||||
await newCardApi(projectId, tags).then((card) => {
|
||||
update((cards) => [...cards, card]);
|
||||
currentModalCard.set(card.id);
|
||||
});
|
||||
},
|
||||
remove: async (card: Card) => {
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
.card {
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #555;
|
||||
margin: 10px;
|
||||
/* width: 200px; */
|
||||
font-family: "Open Sans", sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.card:hover{
|
||||
background-color: #303030;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card .title {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.card .tags {
|
||||
padding-top: 10px;
|
||||
font-weight: lighter;
|
||||
}
|
||||
|
||||
.card .tag {
|
||||
padding: 2px 8px;
|
||||
margin: 4px 4px 0 0;
|
||||
text-transform: uppercase;
|
||||
border-radius: 3px;
|
||||
font-size: 90%;
|
||||
}
|
|
@ -8,3 +8,12 @@ body {
|
|||
color: white;
|
||||
background-color: #252525;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
.modal {
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid green;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal .content {
|
||||
background: #1e1e1e;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
max-width: 1000px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.modal input,
|
||||
.modal textarea {
|
||||
background: none;
|
||||
color: inherit;
|
||||
border: 1px solid #333;
|
||||
border-radius: 7px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.modal .title {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal .header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.modal .buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal button {
|
||||
margin-left: 5px;
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.modal button:hover {
|
||||
background-color: #333;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal .buttons button:first-child:hover {
|
||||
background-color: #343;
|
||||
}
|
||||
|
||||
.modal .buttons button:last-child:hover {
|
||||
background-color: #433;
|
||||
}
|
||||
|
||||
.modal .body {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.modal textarea {
|
||||
width: 100%;
|
||||
min-height: 200px;
|
||||
resize: vertical;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.modal td {
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
.modal td:first-child {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.modal td:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.modal td button {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
#project {
|
||||
padding: 10px 20px;
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
#projects {
|
||||
margin: 40px;
|
||||
}
|
||||
|
||||
#projects h2 {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
#projects ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#projects li {
|
||||
cursor: pointer;
|
||||
margin: 10px 0;
|
||||
border: 1px solid #555;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between
|
||||
}
|
||||
|
||||
#projects .title {
|
||||
font-weight: bold;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#projects .title:hover {
|
||||
background-color: #303030;
|
||||
}
|
||||
|
||||
#projects .buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
}
|
||||
|
||||
#projects li img {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#projects li img:hover {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
#projects input {
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
background-color: #333;
|
||||
color: inherit;
|
||||
font-weight: bold;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
#projects #add {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 20px 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#projects #add:hover {
|
||||
background-color: #303030;
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
#sidebar {
|
||||
width: 240px;
|
||||
background-color: #262a2b;
|
||||
color: white;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#sidebar .logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#sidebar .logo img {
|
||||
max-height: 30px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#sidebar .logo .title {
|
||||
padding-right: 10px;
|
||||
}
|
||||
#sidebar .logo .version {
|
||||
font-size: 0.8em;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
#sidebar .boards h2 {
|
||||
padding-left: 10px;
|
||||
font-size: 0.9em;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#sidebar .boards ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#sidebar a {
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#sidebar .boards ul li:hover {
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
#sidebar .bottom-links {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
#sidebar .bottom-links span {
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
display: block;
|
||||
padding: 10px;
|
||||
font-size: 0.9em;
|
||||
border-top: 1px solid #444;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#sidebar li {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
#sidebar .edit-icon {
|
||||
visibility: hidden;
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#sidebar li:hover .edit-icon {
|
||||
visibility: visible;
|
||||
}
|
Loading…
Reference in New Issue