added CRUD for Projects,
added set project for report
This commit is contained in:
+27
-2
@@ -15,7 +15,20 @@
|
||||
v-model="short_bug"
|
||||
@keyup.enter="onShortBugEnter"
|
||||
/>
|
||||
<button @click="shortBugAdd">
|
||||
<select
|
||||
v-model="selectedProjectId"
|
||||
aria-label="Projekt rýchleho tasku"
|
||||
@change="setSelectedProject(selectedProjectId)"
|
||||
>
|
||||
<option
|
||||
v-for="project in activeProjects"
|
||||
:key="project.project_id"
|
||||
:value="project.project_id"
|
||||
>
|
||||
{{ project.project_name }}
|
||||
</option>
|
||||
</select>
|
||||
<button :disabled="selectedProjectId == null" @click="shortBugAdd">
|
||||
<font-awesome-icon :icon="['fas', 'circle-check']" /> Pridať
|
||||
</button>
|
||||
</div>
|
||||
@@ -44,6 +57,10 @@
|
||||
><font-awesome-icon :icon="['fas', 'box-archive']" />
|
||||
Archív</router-link
|
||||
>
|
||||
<router-link to="/projects"
|
||||
><font-awesome-icon :icon="['fas', 'diagram-project']" />
|
||||
Projekty</router-link
|
||||
>
|
||||
<router-link to="/api"
|
||||
><font-awesome-icon :icon="['fas', 'plug']" /> API</router-link
|
||||
>
|
||||
@@ -66,6 +83,12 @@ import {
|
||||
getDragDropPreference,
|
||||
setDragDropEnabled,
|
||||
} from "./dragDropSettings";
|
||||
import {
|
||||
activeProjects,
|
||||
loadProjects,
|
||||
selectedProjectId,
|
||||
setSelectedProject,
|
||||
} from "./projects";
|
||||
|
||||
const short_bug = ref("");
|
||||
const dragMediaQuery = getDragDropMediaQuery();
|
||||
@@ -88,14 +111,16 @@ function onShortBugEnter(event) {
|
||||
}
|
||||
|
||||
function shortBugAdd() {
|
||||
if (selectedProjectId.value == null) return;
|
||||
let content = short_bug.value;
|
||||
short_bug.value = "";
|
||||
backend.add(content, "", "0", "0", "1").then(() => {
|
||||
backend.add(content, "", "0", selectedProjectId.value, "1").then(() => {
|
||||
events.emit("reports-changed");
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadProjects().catch((error) => console.log(error));
|
||||
if (dragMediaQuery != null) {
|
||||
dragMediaQuery.addEventListener("change", updateAutomaticDragDropState);
|
||||
}
|
||||
|
||||
@@ -75,7 +75,8 @@ h1 {
|
||||
}
|
||||
|
||||
button,
|
||||
.button {
|
||||
.button,
|
||||
select {
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--color-bg0);
|
||||
padding: 0.6em 1.2em;
|
||||
@@ -88,16 +89,27 @@ button,
|
||||
transition: all 0.3s;
|
||||
}
|
||||
button:hover,
|
||||
.button:hover {
|
||||
.button:hover,
|
||||
select:hover {
|
||||
border-color: var(--color-bg1);
|
||||
background-color: var(--color-bg0);
|
||||
}
|
||||
button:focus,
|
||||
.button:focus,
|
||||
select:focus,
|
||||
button:focus-visible,
|
||||
.button:focus-visible {
|
||||
.button:focus-visible,
|
||||
select:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
select:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
select option {
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text0);
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
@@ -141,13 +153,21 @@ button:focus-visible,
|
||||
justify-content: center
|
||||
}
|
||||
#header .short-bug input {
|
||||
width: 80%;
|
||||
width: auto;
|
||||
flex-grow: 1;
|
||||
padding: 5px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
background-color: var(--color-bg2);
|
||||
color: var(--color-text0);
|
||||
}
|
||||
#header .short-bug select {
|
||||
max-width: 180px;
|
||||
margin-left: 5px;
|
||||
padding: 5px 10px;
|
||||
border-color: var(--color-text0);
|
||||
border-radius: 5px;
|
||||
}
|
||||
#header .menu {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -190,7 +210,7 @@ button:focus-visible,
|
||||
background-color: var(--color-bg1);
|
||||
}
|
||||
#header .short-bug button {
|
||||
margin: 0px;
|
||||
margin: 0 0 0 5px;
|
||||
|
||||
/* height: 30px; */
|
||||
}
|
||||
@@ -237,11 +257,27 @@ button:focus-visible,
|
||||
#header .short-bug {
|
||||
flex-direction: column;
|
||||
}
|
||||
#header .short-bug input,
|
||||
#header .short-bug select,
|
||||
#header .short-bug button {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
margin: 3px 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------
|
||||
03 - DASHBOARD
|
||||
*/
|
||||
.dashboard-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
padding: 10px 20px;
|
||||
background-color: var(--color-bg2);
|
||||
}
|
||||
#dashboard {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -307,6 +343,15 @@ button:focus-visible,
|
||||
text-align: left;
|
||||
padding: 5px;
|
||||
}
|
||||
.project-color {
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-right: 5px;
|
||||
border: 1px solid var(--color-text0);
|
||||
border-radius: 50%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
#dashboard .report .report-id {
|
||||
text-align: center;
|
||||
padding: 5px;
|
||||
@@ -571,12 +616,109 @@ button:focus-visible,
|
||||
/* margin-right: 20px; */
|
||||
align-items: center;
|
||||
}
|
||||
#report .report-header select {
|
||||
max-width: 180px;
|
||||
}
|
||||
.project-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------
|
||||
09 - PROJECTS
|
||||
*/
|
||||
#projects {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
#projects .project-new {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
gap: 15px;
|
||||
padding: 15px;
|
||||
background-color: var(--color-bg2);
|
||||
border-radius: 5px;
|
||||
}
|
||||
#projects .project-new .form-group {
|
||||
flex: 1;
|
||||
}
|
||||
#projects .project-new .project-color-field {
|
||||
flex: 0 0 80px;
|
||||
}
|
||||
#projects .project-new input[type="color"] {
|
||||
min-height: 34px;
|
||||
padding: 2px;
|
||||
}
|
||||
#projects .error-message {
|
||||
padding: 10px;
|
||||
background-color: var(--color-bgRed);
|
||||
border-radius: 5px;
|
||||
}
|
||||
#projects .project-list {
|
||||
margin-top: 20px;
|
||||
}
|
||||
#projects .project-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
margin-bottom: 10px;
|
||||
padding: 12px;
|
||||
background-color: var(--color-bg2);
|
||||
border-left: 5px solid var(--color-bg1);
|
||||
border-radius: 5px;
|
||||
}
|
||||
#projects .project-row.archived {
|
||||
opacity: 0.65;
|
||||
border-left-color: var(--color-bgGray);
|
||||
}
|
||||
#projects .project-row > .project-color {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex: 0 0 20px;
|
||||
}
|
||||
#projects .project-data {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
#projects .project-data input[type="text"] {
|
||||
padding: 5px;
|
||||
}
|
||||
#projects .project-data input[type="color"] {
|
||||
margin-top: 5px;
|
||||
}
|
||||
#projects .project-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 110px;
|
||||
}
|
||||
#projects .project-actions {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
#projects .project-system {
|
||||
font-style: italic;
|
||||
}
|
||||
@media (max-width: 800px) {
|
||||
#projects .project-new,
|
||||
#projects .project-row {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
#projects .project-actions {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
#report .report-header div span {
|
||||
background-color: var(--color-bg0);
|
||||
color: var(--color-text0);
|
||||
padding: 2px 10px;
|
||||
align-items: center;
|
||||
}
|
||||
#report .report-header div span.project-color {
|
||||
padding: 0;
|
||||
}
|
||||
#report .report-header div strong {
|
||||
background-color: var(--color-bg1);
|
||||
color: var(--color-text0);
|
||||
@@ -644,6 +786,10 @@ button:focus-visible,
|
||||
background-color: var(--color-bg2);
|
||||
color: var(--color-text0);
|
||||
}
|
||||
.form-group select {
|
||||
width: 99%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.form-actions {
|
||||
margin-top: 10px;
|
||||
text-align: right;
|
||||
|
||||
+29
-9
@@ -2,7 +2,7 @@
|
||||
* Generated by APIlite
|
||||
* https://gitea.tpsoft.org/TPsoft.org/APIlite
|
||||
*
|
||||
* 2026-07-07 22:15:38 */
|
||||
* 2026-08-22 20:42:16 */
|
||||
|
||||
class backend {
|
||||
endpoint = import.meta.env.VITE_BACKENDAPI_URL;
|
||||
@@ -154,8 +154,8 @@ class backend {
|
||||
return this.callPromise('__HELP__', {});
|
||||
}
|
||||
|
||||
add(title, description, status, group, priority) {
|
||||
return this.callPromise('add', {title: title, description: description, status: status, group: group, priority: priority});
|
||||
add(title, description, status, project_id, priority) {
|
||||
return this.callPromise('add', {title: title, description: description, status: status, project_id: project_id, priority: priority});
|
||||
}
|
||||
|
||||
update(report_id, report_data) {
|
||||
@@ -170,16 +170,16 @@ class backend {
|
||||
return this.callPromise('get', {report_id: report_id});
|
||||
}
|
||||
|
||||
getAll(status, page) {
|
||||
return this.callPromise('getAll', {status: status, page: page});
|
||||
getAll(status, page, project_id) {
|
||||
return this.callPromise('getAll', {status: status, page: page, project_id: project_id});
|
||||
}
|
||||
|
||||
getAllGrouped(status, page) {
|
||||
return this.callPromise('getAllGrouped', {status: status, page: page});
|
||||
getAllGrouped(status, page, project_id) {
|
||||
return this.callPromise('getAllGrouped', {status: status, page: page, project_id: project_id});
|
||||
}
|
||||
|
||||
getArchived(page) {
|
||||
return this.callPromise('getArchived', {page: page});
|
||||
getArchived(page, project_id) {
|
||||
return this.callPromise('getArchived', {page: page, project_id: project_id});
|
||||
}
|
||||
|
||||
updateOrdNum(ordnums) {
|
||||
@@ -190,6 +190,26 @@ class backend {
|
||||
return this.callPromise('updateStatus', {report_id: report_id, status: status});
|
||||
}
|
||||
|
||||
projectGetAll(include_archived) {
|
||||
return this.callPromise('projectGetAll', {include_archived: include_archived});
|
||||
}
|
||||
|
||||
projectGet(project_id) {
|
||||
return this.callPromise('projectGet', {project_id: project_id});
|
||||
}
|
||||
|
||||
projectAdd(name, code, color) {
|
||||
return this.callPromise('projectAdd', {name: name, code: code, color: color});
|
||||
}
|
||||
|
||||
projectUpdate(project_id, name, color) {
|
||||
return this.callPromise('projectUpdate', {project_id: project_id, name: name, color: color});
|
||||
}
|
||||
|
||||
projectArchive(project_id, archived) {
|
||||
return this.callPromise('projectArchive', {project_id: project_id, archived: archived});
|
||||
}
|
||||
|
||||
attachmentAdd(report_id, attachment_type, attachment_content) {
|
||||
return this.callPromise('attachmentAdd', {report_id: report_id, attachment_type: attachment_type, attachment_content: attachment_content});
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ defineProps({
|
||||
description: String,
|
||||
date: String,
|
||||
priority: Number,
|
||||
group: String,
|
||||
projectName: String,
|
||||
projectColor: String,
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
@@ -21,7 +22,10 @@ defineProps({
|
||||
</p>
|
||||
</div>
|
||||
<div class="report-footer">
|
||||
<div class="report-group"><font-awesome-icon :icon="['fas', 'diagram-project']" /> {{ group }}</div>
|
||||
<div class="report-group">
|
||||
<span class="project-color" :style="{ backgroundColor: projectColor }"></span>
|
||||
<font-awesome-icon :icon="['fas', 'diagram-project']" /> {{ projectName }}
|
||||
</div>
|
||||
<div class="report-id"><font-awesome-icon :icon="['fas', 'hashtag']" /> {{ report_id }}</div>
|
||||
<div class="report-date"><font-awesome-icon :icon="['fas', 'calendar-days']" /> {{ date }}</div>
|
||||
</div>
|
||||
@@ -42,4 +46,4 @@ export default {
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
const DRAG_DROP_STORAGE_KEY = "bugreport_drag_drop_enabled";
|
||||
const DRAG_DROP_MEDIA_QUERY = "(max-width: 768px), (any-pointer: coarse)";
|
||||
|
||||
let sessionDragDropPreference = null;
|
||||
|
||||
function getStorage() {
|
||||
try {
|
||||
if (typeof window !== "undefined" && window.localStorage) {
|
||||
return window.localStorage;
|
||||
}
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getDragDropMediaQuery() {
|
||||
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
||||
return null;
|
||||
}
|
||||
return window.matchMedia(DRAG_DROP_MEDIA_QUERY);
|
||||
}
|
||||
|
||||
export function getDragDropPreference() {
|
||||
if (sessionDragDropPreference != null) return sessionDragDropPreference;
|
||||
|
||||
let storage = getStorage();
|
||||
if (storage != null) {
|
||||
try {
|
||||
let storedPreference = storage.getItem(DRAG_DROP_STORAGE_KEY);
|
||||
if (storedPreference === "true") return true;
|
||||
if (storedPreference === "false") return false;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getDragDropEnabled(mediaQuery = null) {
|
||||
let preference = getDragDropPreference();
|
||||
if (preference != null) return preference;
|
||||
|
||||
if (mediaQuery == null) mediaQuery = getDragDropMediaQuery();
|
||||
return mediaQuery == null || !mediaQuery.matches;
|
||||
}
|
||||
|
||||
export function setDragDropEnabled(enabled) {
|
||||
sessionDragDropPreference = enabled;
|
||||
let storage = getStorage();
|
||||
if (storage == null) return;
|
||||
|
||||
try {
|
||||
storage.setItem(DRAG_DROP_STORAGE_KEY, String(enabled));
|
||||
} catch (error) {
|
||||
// The in-memory preference still applies until the page is reloaded.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { computed, ref } from "vue";
|
||||
import backend from "./backend";
|
||||
|
||||
const SELECTED_PROJECT_KEY = "bugreport_selected_project_id";
|
||||
|
||||
export const projects = ref([]);
|
||||
export const projectsLoading = ref(false);
|
||||
export const activeProjects = computed(() =>
|
||||
projects.value.filter((project) => Number(project.project_active) === 1)
|
||||
);
|
||||
export const selectedProjectId = ref(readSelectedProjectId());
|
||||
|
||||
let loadPromise = null;
|
||||
|
||||
function getStorage() {
|
||||
try {
|
||||
if (typeof window !== "undefined" && window.localStorage) {
|
||||
return window.localStorage;
|
||||
}
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function readSelectedProjectId() {
|
||||
let storage = getStorage();
|
||||
if (storage == null) return null;
|
||||
try {
|
||||
let projectId = Number(storage.getItem(SELECTED_PROJECT_KEY));
|
||||
return Number.isInteger(projectId) && projectId > 0 ? projectId : null;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function ensureSelectedProject() {
|
||||
let selected = activeProjects.value.find(
|
||||
(project) => Number(project.project_id) === Number(selectedProjectId.value)
|
||||
);
|
||||
if (selected != null) return;
|
||||
let fallback = activeProjects.value.find(
|
||||
(project) => project.project_code === "unassigned"
|
||||
) ?? activeProjects.value[0];
|
||||
setSelectedProject(fallback == null ? null : fallback.project_id);
|
||||
}
|
||||
|
||||
export function setSelectedProject(projectId) {
|
||||
selectedProjectId.value = projectId == null ? null : Number(projectId);
|
||||
let storage = getStorage();
|
||||
if (storage == null) return;
|
||||
try {
|
||||
if (selectedProjectId.value == null) {
|
||||
storage.removeItem(SELECTED_PROJECT_KEY);
|
||||
return;
|
||||
}
|
||||
storage.setItem(SELECTED_PROJECT_KEY, String(selectedProjectId.value));
|
||||
} catch (error) {
|
||||
// The selected project still applies until the page is reloaded.
|
||||
}
|
||||
}
|
||||
|
||||
export function loadProjects() {
|
||||
if (loadPromise != null) return loadPromise;
|
||||
projectsLoading.value = true;
|
||||
loadPromise = backend.projectGetAll(true)
|
||||
.then((response) => {
|
||||
projects.value = response.data;
|
||||
ensureSelectedProject();
|
||||
return projects.value;
|
||||
})
|
||||
.finally(() => {
|
||||
projectsLoading.value = false;
|
||||
loadPromise = null;
|
||||
});
|
||||
return loadPromise;
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import BugAdd from "./views/BugAdd.vue";
|
||||
import Archive from "./views/Archive.vue";
|
||||
import API from "./views/API.vue";
|
||||
import Report from "./views/Report.vue";
|
||||
import Projects from "./views/Projects.vue";
|
||||
|
||||
const routes = [
|
||||
{ path: "/", component: Dashboard },
|
||||
@@ -14,6 +15,7 @@ const routes = [
|
||||
{ path: "/archive", component: Archive },
|
||||
{ path: "/api", component: API },
|
||||
{ path: "/report/:id", component: Report },
|
||||
{ path: "/projects", component: Projects },
|
||||
];
|
||||
|
||||
export const router = createRouter({
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
</div>
|
||||
<div class="title">{{ report.report_title }}</div>
|
||||
<div class="date">{{ report.created_dt }}</div>
|
||||
<div class="group">{{ report.report_group }}</div>
|
||||
<div class="group">
|
||||
<span class="project-color" :style="{ backgroundColor: report.project_color }"></span>
|
||||
{{ report.project_name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -47,18 +47,22 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="group">Skupina:</label>
|
||||
<label for="project">Projekt:</label>
|
||||
<select
|
||||
id="group"
|
||||
v-model="bugReport.group"
|
||||
id="project"
|
||||
v-model="bugReport.project_id"
|
||||
class="form-control"
|
||||
required
|
||||
@change="rememberProject"
|
||||
>
|
||||
<option value="" disabled>Vyberte skupinu</option>
|
||||
<option value="cp">Control Panel</option>
|
||||
<option value="task">Task.Platon.sk</option>
|
||||
<option value="websiteip">WebsiteIP</option>
|
||||
<option value="antispam">Antispam</option>
|
||||
<option value="" disabled>Vyberte projekt</option>
|
||||
<option
|
||||
v-for="project in projects"
|
||||
:key="project.project_id"
|
||||
:value="project.project_id"
|
||||
>
|
||||
{{ project.project_name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -81,24 +85,46 @@
|
||||
<script>
|
||||
import backend from "../backend";
|
||||
import FullScreenLoader from "../components/FullScreenLoader.vue";
|
||||
import {
|
||||
activeProjects,
|
||||
loadProjects,
|
||||
selectedProjectId,
|
||||
setSelectedProject,
|
||||
} from "../projects";
|
||||
|
||||
export default {
|
||||
name: "BugAdd",
|
||||
components: { FullScreenLoader },
|
||||
mounted() {
|
||||
this.loading = true;
|
||||
loadProjects()
|
||||
.then(() => {
|
||||
this.projects = activeProjects.value;
|
||||
this.bugReport.project_id = selectedProjectId.value;
|
||||
})
|
||||
.catch((error) => console.log(error))
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
bugReport: {
|
||||
title: "",
|
||||
description: "",
|
||||
priority: "1",
|
||||
group: "cp",
|
||||
project_id: null,
|
||||
files: [],
|
||||
},
|
||||
selectedFiles: [],
|
||||
projects: [],
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
rememberProject() {
|
||||
setSelectedProject(this.bugReport.project_id);
|
||||
},
|
||||
submitForm() {
|
||||
this.loading = true;
|
||||
// Vytvorenie FormData objektu pre odoslanie súborov
|
||||
@@ -118,7 +144,7 @@ export default {
|
||||
this.bugReport.title,
|
||||
this.bugReport.description,
|
||||
"0",
|
||||
this.bugReport.group,
|
||||
this.bugReport.project_id,
|
||||
this.bugReport.priority
|
||||
)
|
||||
.then((result) => {
|
||||
@@ -137,7 +163,8 @@ export default {
|
||||
this.bugReport = {
|
||||
title: "",
|
||||
description: "",
|
||||
priority: "",
|
||||
priority: "1",
|
||||
project_id: selectedProjectId.value,
|
||||
files: [],
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
<template>
|
||||
<FullScreenLoader v-if="loading" />
|
||||
|
||||
<div class="dashboard-toolbar">
|
||||
<label for="dashboard-project">Projekt:</label>
|
||||
<select id="dashboard-project" v-model="projectFilter" @change="loadData()">
|
||||
<option value="">Všetky projekty</option>
|
||||
<option
|
||||
v-for="project in projects"
|
||||
:key="project.project_id"
|
||||
:value="project.project_id"
|
||||
>
|
||||
{{ project.project_name }}{{ Number(project.project_active) === 1 ? "" : " (archivovaný)" }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="dashboard" :class="{ 'drag-disabled': isDragDisabled }">
|
||||
<div id="inbox">
|
||||
<h2>Nezaradené</h2>
|
||||
@@ -26,7 +40,8 @@
|
||||
:description="element.report_description"
|
||||
:date="element.created_dt"
|
||||
:priority="element.report_priority"
|
||||
:group="element.report_group"
|
||||
:project-name="element.project_name"
|
||||
:project-color="element.project_color"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -56,7 +71,8 @@
|
||||
:description="element.report_description"
|
||||
:date="element.created_dt"
|
||||
:priority="element.report_priority"
|
||||
:group="element.report_group"
|
||||
:project-name="element.project_name"
|
||||
:project-color="element.project_color"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -86,7 +102,8 @@
|
||||
:description="element.report_description"
|
||||
:date="element.created_dt"
|
||||
:priority="element.report_priority"
|
||||
:group="element.report_group"
|
||||
:project-name="element.project_name"
|
||||
:project-color="element.project_color"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -116,7 +133,8 @@
|
||||
:description="element.report_description"
|
||||
:date="element.created_dt"
|
||||
:priority="element.report_priority"
|
||||
:group="element.report_group"
|
||||
:project-name="element.project_name"
|
||||
:project-color="element.project_color"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -150,6 +168,7 @@ import {
|
||||
getDragDropEnabled,
|
||||
getDragDropMediaQuery,
|
||||
} from "../dragDropSettings";
|
||||
import { loadProjects, projects } from "../projects";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -166,6 +185,8 @@ export default {
|
||||
itemsWaiting: [],
|
||||
itemsInProgress: [],
|
||||
itemsBlocked: [],
|
||||
projectFilter: "",
|
||||
projects: [],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
@@ -181,12 +202,14 @@ export default {
|
||||
},
|
||||
loadData(use_loader = true) {
|
||||
if (use_loader) this.loading = true;
|
||||
backend.getAllGrouped(Array(0, 1, 2, 3)).then((response) => {
|
||||
let project_id = this.projectFilter === "" ? undefined : this.projectFilter;
|
||||
backend.getAllGrouped(Array(0, 1, 2, 3), 0, project_id).then((response) => {
|
||||
let all_grouped = response.data;
|
||||
this.itemsUncategorized = all_grouped[0];
|
||||
this.itemsWaiting = all_grouped[1];
|
||||
this.itemsInProgress = all_grouped[2];
|
||||
this.itemsBlocked = all_grouped[3];
|
||||
this.itemsUncategorized = all_grouped[0] ?? [];
|
||||
this.itemsWaiting = all_grouped[1] ?? [];
|
||||
this.itemsInProgress = all_grouped[2] ?? [];
|
||||
this.itemsBlocked = all_grouped[3] ?? [];
|
||||
}).catch((error) => console.log(error)).finally(() => {
|
||||
if (use_loader) this.loading = false;
|
||||
});
|
||||
},
|
||||
@@ -258,6 +281,9 @@ export default {
|
||||
this.dragMediaQuery.addEventListener("change", this.updateDragState);
|
||||
}
|
||||
this.loadData();
|
||||
loadProjects().then(() => {
|
||||
this.projects = projects.value;
|
||||
}).catch((error) => console.log(error));
|
||||
events.on("reports-changed", this.onReportsChanged);
|
||||
events.on("drag-drop-changed", this.updateDragPreference);
|
||||
},
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<FullScreenLoader v-if="loading" />
|
||||
|
||||
<div id="projects">
|
||||
<h1>Projekty</h1>
|
||||
|
||||
<form class="form project-new" @submit.prevent="projectAdd">
|
||||
<div class="form-group">
|
||||
<label for="project-name">Názov:</label>
|
||||
<input
|
||||
id="project-name"
|
||||
v-model="newProject.name"
|
||||
type="text"
|
||||
required
|
||||
maxlength="255"
|
||||
@input="suggestProjectCode"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="project-code">Kód:</label>
|
||||
<input
|
||||
id="project-code"
|
||||
v-model="newProject.code"
|
||||
type="text"
|
||||
required
|
||||
maxlength="64"
|
||||
pattern="[a-z0-9][a-z0-9_-]{0,63}"
|
||||
@input="codeEdited = true"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group project-color-field">
|
||||
<label for="project-color">Farba:</label>
|
||||
<input id="project-color" v-model="newProject.color" type="color" />
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit">
|
||||
<font-awesome-icon :icon="['fas', 'circle-plus']" /> Pridať projekt
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p v-if="errorMessage" class="error-message">{{ errorMessage }}</p>
|
||||
|
||||
<div class="project-list">
|
||||
<div
|
||||
v-for="project in projects"
|
||||
:key="project.project_id"
|
||||
class="project-row"
|
||||
:class="{ archived: Number(project.project_active) !== 1 }"
|
||||
>
|
||||
<span class="project-color" :style="{ backgroundColor: project.project_color }"></span>
|
||||
<div class="project-data">
|
||||
<template v-if="editingProjectId === project.project_id">
|
||||
<input v-model="editProject.name" type="text" maxlength="255" required />
|
||||
<input v-model="editProject.color" type="color" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<strong>{{ project.project_name }}</strong>
|
||||
<code>{{ project.project_code }}</code>
|
||||
</template>
|
||||
</div>
|
||||
<div class="project-meta">
|
||||
{{ project.report_count }} taskov
|
||||
<span v-if="Number(project.project_active) !== 1">Archivovaný</span>
|
||||
</div>
|
||||
<div class="project-actions" v-if="Number(project.project_system) !== 1">
|
||||
<template v-if="editingProjectId === project.project_id">
|
||||
<button @click="projectUpdate(project)">Uložiť</button>
|
||||
<button @click="editingProjectId = null">Zrušiť</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button @click="startEdit(project)">
|
||||
<font-awesome-icon :icon="['fas', 'pen']" /> Upraviť
|
||||
</button>
|
||||
<button
|
||||
v-if="Number(project.project_active) === 1"
|
||||
@click="projectArchive(project, true)"
|
||||
>
|
||||
<font-awesome-icon :icon="['fas', 'box-archive']" /> Archivovať
|
||||
</button>
|
||||
<button v-else @click="projectArchive(project, false)">
|
||||
<font-awesome-icon :icon="['fas', 'rotate-left']" /> Obnoviť
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
<div v-else class="project-actions project-system">Systémový projekt</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from "vue";
|
||||
import backend from "../backend";
|
||||
import FullScreenLoader from "../components/FullScreenLoader.vue";
|
||||
import { loadProjects, projects } from "../projects";
|
||||
|
||||
const loading = ref(false);
|
||||
const errorMessage = ref("");
|
||||
const codeEdited = ref(false);
|
||||
const editingProjectId = ref(null);
|
||||
const newProject = reactive({ name: "", code: "", color: "#449EF8" });
|
||||
const editProject = reactive({ name: "", color: "#449EF8" });
|
||||
|
||||
refreshProjects();
|
||||
|
||||
function slugify(value) {
|
||||
return value
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9_-]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "")
|
||||
.substring(0, 64);
|
||||
}
|
||||
|
||||
function suggestProjectCode() {
|
||||
if (!codeEdited.value) newProject.code = slugify(newProject.name);
|
||||
}
|
||||
|
||||
function refreshProjects() {
|
||||
loading.value = true;
|
||||
return loadProjects()
|
||||
.catch(showError)
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function projectAdd() {
|
||||
loading.value = true;
|
||||
errorMessage.value = "";
|
||||
backend.projectAdd(newProject.name, newProject.code, newProject.color)
|
||||
.then(() => {
|
||||
newProject.name = "";
|
||||
newProject.code = "";
|
||||
newProject.color = "#449EF8";
|
||||
codeEdited.value = false;
|
||||
return refreshProjects();
|
||||
})
|
||||
.catch(showError)
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function startEdit(project) {
|
||||
editingProjectId.value = project.project_id;
|
||||
editProject.name = project.project_name;
|
||||
editProject.color = project.project_color;
|
||||
}
|
||||
|
||||
function projectUpdate(project) {
|
||||
loading.value = true;
|
||||
errorMessage.value = "";
|
||||
backend.projectUpdate(project.project_id, editProject.name, editProject.color)
|
||||
.then(() => {
|
||||
editingProjectId.value = null;
|
||||
return refreshProjects();
|
||||
})
|
||||
.catch(showError)
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function projectArchive(project, archived) {
|
||||
let action = archived ? "archivovať" : "obnoviť";
|
||||
if (!confirm(`Naozaj chcete ${action} projekt ${project.project_name}? Projekt obsahuje ${project.report_count} taskov.`)) return;
|
||||
loading.value = true;
|
||||
errorMessage.value = "";
|
||||
backend.projectArchive(project.project_id, archived)
|
||||
.then(() => refreshProjects())
|
||||
.catch(showError)
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function showError(error) {
|
||||
errorMessage.value = String(error);
|
||||
}
|
||||
</script>
|
||||
@@ -20,8 +20,31 @@
|
||||
<strong>{{ report.report_priority }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Skupina</span>
|
||||
<strong>{{ report.report_group }}</strong>
|
||||
<span>Projekt</span>
|
||||
<span
|
||||
v-if="editable"
|
||||
class="project-color"
|
||||
:style="{ backgroundColor: report.project_color }"
|
||||
></span>
|
||||
<select
|
||||
v-if="editable"
|
||||
v-model="report.project_id"
|
||||
aria-label="Projekt tasku"
|
||||
@change="onProjectChange"
|
||||
>
|
||||
<option
|
||||
v-for="project in availableProjects"
|
||||
:key="project.project_id"
|
||||
:value="project.project_id"
|
||||
:disabled="Number(project.project_active) !== 1"
|
||||
>
|
||||
{{ project.project_name }}{{ Number(project.project_active) === 1 ? "" : " (archivovaný)" }}
|
||||
</option>
|
||||
</select>
|
||||
<strong v-else class="project-label">
|
||||
<span class="project-color" :style="{ backgroundColor: report.project_color }"></span>
|
||||
{{ report.project_name }}
|
||||
</strong>
|
||||
</div>
|
||||
<div>
|
||||
<button @click="reportDelete">
|
||||
@@ -145,11 +168,12 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref } from "vue";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { router } from "../router";
|
||||
import backend from "../backend";
|
||||
import FullScreenLoader from "../components/FullScreenLoader.vue";
|
||||
import JSConfetti from 'js-confetti'
|
||||
import { loadProjects, projects } from "../projects";
|
||||
|
||||
let tadas = ['/sounds/tada.mp3', '/sounds/tada2.mp3', '/sounds/crazy-phrog-short.mp3'];
|
||||
const jungle = new Audio(tadas[Math.floor(Math.random() * tadas.length)]);
|
||||
@@ -161,7 +185,11 @@ const report = ref({
|
||||
report_title: "Nacitavam report",
|
||||
report_description: "...",
|
||||
report_status: 4,
|
||||
report_group: "--",
|
||||
project_id: null,
|
||||
project_name: "--",
|
||||
project_code: "--",
|
||||
project_color: "#797979",
|
||||
project_active: 1,
|
||||
report_priority: 1,
|
||||
created_dt: "--",
|
||||
ordnum: 0,
|
||||
@@ -179,9 +207,14 @@ const attachmentNewContent = ref(null);
|
||||
const attachmentNewFiles = ref(null);
|
||||
const selectedFiles = ref([]);
|
||||
const selectedFilesContent = ref([]);
|
||||
const availableProjects = computed(() => projects.value.filter((project) =>
|
||||
Number(project.project_active) === 1
|
||||
|| Number(project.project_id) === Number(report.value.project_id)
|
||||
));
|
||||
|
||||
onMounted(() => {
|
||||
// console.log(report_id);
|
||||
loadProjects().catch((error) => console.log(error));
|
||||
loadReportData();
|
||||
});
|
||||
|
||||
@@ -207,6 +240,20 @@ function onDescriptionChange(event) {
|
||||
});
|
||||
}
|
||||
|
||||
function onProjectChange() {
|
||||
loading.value = true;
|
||||
backend.update(report_id, {
|
||||
project_id: report.value.project_id,
|
||||
}).then(() => {
|
||||
loadReportData();
|
||||
}).catch((error) => {
|
||||
console.log(error);
|
||||
loadReportData();
|
||||
}).finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function reportDelete() {
|
||||
if (!confirm("Naozaj chcete report zmazať?")) return;
|
||||
loading.value = true;
|
||||
|
||||
Reference in New Issue
Block a user