chore: initial update to vue 3 and compositions api
|
@ -1,3 +1,3 @@
|
|||
> 1%
|
||||
last 2 versions
|
||||
not ie <= 8
|
||||
not dead
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
extends: [
|
||||
"plugin:vue/vue3-essential",
|
||||
"eslint:recommended",
|
||||
"@vue/typescript/recommended",
|
||||
"@vue/prettier",
|
||||
"@vue/prettier/@typescript-eslint",
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
},
|
||||
rules: {
|
||||
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||
},
|
||||
};
|
|
@ -2,6 +2,7 @@
|
|||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
@ -10,6 +11,7 @@ node_modules
|
|||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
|
@ -18,4 +20,4 @@ yarn-error.log*
|
|||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw*
|
||||
*.sw?
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# rpstrackervue
|
||||
# kendo-video
|
||||
|
||||
## Project setup
|
||||
```
|
||||
|
@ -15,11 +15,6 @@ npm run serve
|
|||
npm run build
|
||||
```
|
||||
|
||||
### Run your tests
|
||||
```
|
||||
npm run test
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
presets: ["@vue/cli-plugin-babel/preset"],
|
||||
};
|
|
@ -1 +0,0 @@
|
|||
<YOUR LICENSE>
|
33
package.json
|
@ -1,25 +1,34 @@
|
|||
{
|
||||
"name": "rpstrackervue",
|
||||
"name": "kendo-video",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint --no-fix"
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@progress/kendo-licensing": "^1.1.4",
|
||||
"bootstrap-vue": "2.21.2",
|
||||
"bootstrap": "4.6.0",
|
||||
"rxjs": "6.4.0",
|
||||
"vue": "2.6.12",
|
||||
"vue-class-component": "7.2.6",
|
||||
"vue-property-decorator": "9.1.2",
|
||||
"vue-router": "3.5.1"
|
||||
"core-js": "^3.6.5",
|
||||
"vue": "^3.0.0",
|
||||
"vue-router": "^4.0.0-0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-typescript": "4.5.12",
|
||||
"@vue/cli-service": "4.5.12",
|
||||
"typescript": "4.2.4",
|
||||
"vue-template-compiler": "2.6.12"
|
||||
"@typescript-eslint/eslint-plugin": "^4.18.0",
|
||||
"@typescript-eslint/parser": "^4.18.0",
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-plugin-router": "~4.5.0",
|
||||
"@vue/cli-plugin-typescript": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/compiler-sfc": "^3.0.0",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"@vue/eslint-config-typescript": "^7.0.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"eslint-plugin-vue": "^7.0.0",
|
||||
"prettier": "^2.2.1",
|
||||
"typescript": "~4.1.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {}
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
module.exports = {
|
||||
trailingComma: "es5",
|
||||
tabWidth: 4,
|
||||
semi: true,
|
||||
singleQuote: true
|
||||
};
|
Двоичные данные
public/favicon.ico
До Ширина: | Высота: | Размер: 1.1 KiB После Ширина: | Высота: | Размер: 4.2 KiB |
Двоичные данные
public/img/icon_bug.png
До Ширина: | Высота: | Размер: 17 KiB |
Двоичные данные
public/img/icon_chore.png
До Ширина: | Высота: | Размер: 31 KiB |
Двоичные данные
public/img/icon_impediment.png
До Ширина: | Высота: | Размер: 17 KiB |
Двоичные данные
public/img/icon_pbi.png
До Ширина: | Высота: | Размер: 25 KiB |
|
@ -1,15 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title>rpstrackervue</title>
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but rpstrackervue doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
|
|
23
src/App.vue
|
@ -15,25 +15,18 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
import SideMenu from '@/components/SideMenu.vue';
|
||||
import MainMenu from '@/components/MainMenu.vue';
|
||||
|
||||
/*
|
||||
Component.registerHooks([
|
||||
"beforeRouteEnter",
|
||||
"beforeRouteLeave",
|
||||
"beforeRouteUpdate"
|
||||
]);
|
||||
*/
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
MainMenu,
|
||||
SideMenu,
|
||||
},
|
||||
})
|
||||
export default class App extends Vue {}
|
||||
export default defineComponent({
|
||||
name: "App",
|
||||
components: {
|
||||
MainMenu,
|
||||
SideMenu,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
|
После Ширина: | Высота: | Размер: 6.7 KiB |
|
@ -0,0 +1,132 @@
|
|||
<template>
|
||||
<div class="hello">
|
||||
<h1>{{ msg }}</h1>
|
||||
<p>
|
||||
For a guide and recipes on how to configure / customize this project,<br />
|
||||
check out the
|
||||
<a href="https://cli.vuejs.org" target="_blank" rel="noopener"
|
||||
>vue-cli documentation</a
|
||||
>.
|
||||
</p>
|
||||
<h3>Installed CLI Plugins</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>babel</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>router</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>eslint</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>typescript</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Essential Links</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://forum.vuejs.org" target="_blank" rel="noopener"
|
||||
>Forum</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://chat.vuejs.org" target="_blank" rel="noopener"
|
||||
>Community Chat</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener"
|
||||
>Twitter</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Ecosystem</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://router.vuejs.org" target="_blank" rel="noopener"
|
||||
>vue-router</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://github.com/vuejs/vue-devtools#vue-devtools"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>vue-devtools</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener"
|
||||
>vue-loader</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://github.com/vuejs/awesome-vue"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>awesome-vue</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "HelloWorld",
|
||||
props: {
|
||||
msg: String,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
h3 {
|
||||
margin: 40px 0 0;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
</style>
|
|
@ -9,10 +9,3 @@
|
|||
</nav>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
@Component
|
||||
export default class MainMenu extends Vue {}
|
||||
</script>
|
||||
|
|
|
@ -17,15 +17,20 @@
|
|||
>Done Items</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { PresetType } from '@/core/models/domain/types';
|
||||
import { defineComponent } from "vue";
|
||||
import { PresetType } from "@/core/models/domain/types";
|
||||
|
||||
@Component
|
||||
export default class PresetFilter extends Vue {
|
||||
public onSelectPresetTap(preset: PresetType) {
|
||||
this.$emit('onPresetSelected', preset);
|
||||
}
|
||||
}
|
||||
export default defineComponent({
|
||||
name: "PresetFilter",
|
||||
setup(_props, context) {
|
||||
const onSelectPresetTap = (preset: PresetType) => {
|
||||
context.emit("onPresetSelected", preset);
|
||||
};
|
||||
|
||||
return {
|
||||
onSelectPresetTap,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -252,8 +252,9 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
@Component
|
||||
export default class SideMenu extends Vue {}
|
||||
export default defineComponent({
|
||||
name: "SideMenu",
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -41,13 +41,15 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop } from 'vue-property-decorator';
|
||||
import { defineComponent, PropType } from "vue";
|
||||
import { StatusCounts } from '@/shared/models/ui/stats';
|
||||
|
||||
@Component
|
||||
export default class ActiveIssues extends Vue {
|
||||
@Prop() public statusCounts!: StatusCounts;
|
||||
}
|
||||
export default defineComponent({
|
||||
name: "ActiveIssues",
|
||||
props: {
|
||||
statusCounts: Object as PropType<StatusCounts>,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -40,42 +40,51 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop, Emit, Watch } from 'vue-property-decorator';
|
||||
import { defineComponent, PropType, ref } from "vue";
|
||||
import { EMPTY_STRING } from '@/core/helpers/string-helpers';
|
||||
import { PtComment, PtUser } from '@/core/models/domain';
|
||||
import { PtNewComment } from '@/shared/models/dto/pt-new-comment';
|
||||
|
||||
@Component
|
||||
export default class PtItemChitchat extends Vue {
|
||||
@Prop() public comments!: PtComment[];
|
||||
@Prop() public currentUser!: PtUser;
|
||||
export default defineComponent({
|
||||
name: "PtItemChitchat",
|
||||
props: {
|
||||
comments: Array as PropType<PtComment[]>,
|
||||
currentUser: Object as PropType<PtUser>,
|
||||
},
|
||||
setup() {
|
||||
const newCommentText = ref(EMPTY_STRING);
|
||||
const addNewComment = (newComment: PtNewComment) => {
|
||||
console.log("add new comment" + newComment);
|
||||
};
|
||||
|
||||
public newCommentText = EMPTY_STRING;
|
||||
@Emit('addNewComment')
|
||||
public addNewComment(newComment: PtNewComment) {}
|
||||
const onAddTapped = () => {
|
||||
const newTitle = newCommentText.value.trim();
|
||||
if (newTitle.length === 0) {
|
||||
return;
|
||||
}
|
||||
const newComment: PtNewComment = {
|
||||
title: newTitle,
|
||||
};
|
||||
addNewComment(newComment);
|
||||
|
||||
public onAddTapped() {
|
||||
const newTitle = this.newCommentText.trim();
|
||||
if (newTitle.length === 0) {
|
||||
return;
|
||||
}
|
||||
const newComment: PtNewComment = {
|
||||
title: newTitle,
|
||||
};
|
||||
this.addNewComment(newComment);
|
||||
newCommentText.value = EMPTY_STRING;
|
||||
};
|
||||
|
||||
this.newCommentText = EMPTY_STRING;
|
||||
}
|
||||
}
|
||||
return {
|
||||
newCommentText,
|
||||
onAddTapped,
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.chitchat-item {
|
||||
margin-top: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.chitchat-text {
|
||||
color: #495057;
|
||||
font-size: 0.9em;
|
||||
color: #495057;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -83,7 +83,7 @@
|
|||
<label class="col-sm-2 col-form-label">Assignee</label>
|
||||
|
||||
<div class="col-sm-10">
|
||||
<img :src="this.selectedAssignee.avatar" class="li-avatar rounded">
|
||||
<img :src="selectedAssignee.avatar" class="li-avatar rounded">
|
||||
<span>{{itemForm.assigneeName}}</span>
|
||||
|
||||
<button
|
||||
|
@ -131,107 +131,125 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop, Emit } from 'vue-property-decorator';
|
||||
import { Observable } from 'rxjs';
|
||||
import { defineComponent, PropType, ref, toRefs } from "vue";
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { PtItem, PtUser } from '@/core/models/domain';
|
||||
import { PtItem, PtUser } from "@/core/models/domain";
|
||||
import {
|
||||
ItemType,
|
||||
PT_ITEM_STATUSES,
|
||||
PT_ITEM_PRIORITIES,
|
||||
} from '@/core/constants';
|
||||
ItemType,
|
||||
PT_ITEM_STATUSES,
|
||||
PT_ITEM_PRIORITIES,
|
||||
} from "@/core/constants";
|
||||
import {
|
||||
PtItemDetailsEditFormModel,
|
||||
ptItemToFormModel,
|
||||
} from '@/shared/models/forms/pt-item-details-edit-form';
|
||||
import { EMPTY_STRING } from '@/core/helpers';
|
||||
import { PriorityEnum, StatusEnum } from '@/core/models/domain/enums';
|
||||
PtItemDetailsEditFormModel,
|
||||
ptItemToFormModel,
|
||||
} from "@/shared/models/forms/pt-item-details-edit-form";
|
||||
|
||||
@Component
|
||||
export default class PtItemDetails extends Vue {
|
||||
@Prop() public item!: PtItem;
|
||||
@Prop() public usersObs!: Observable<PtUser[]>;
|
||||
export default defineComponent({
|
||||
name: "PtItemChitchat",
|
||||
props: {
|
||||
item: {
|
||||
type: Object as PropType<PtItem>,
|
||||
default: () => ({
|
||||
description: "",
|
||||
}),
|
||||
},
|
||||
usersObs: {
|
||||
type: Object as PropType<Observable<PtUser[]>>,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
setup(props, context) {
|
||||
const itemTypesProvider = ref(ItemType.List.map((t) => t.PtItemType));
|
||||
const statusesProvider = ref(PT_ITEM_STATUSES);
|
||||
const prioritiesProvider = ref(PT_ITEM_PRIORITIES);
|
||||
|
||||
public itemTypesProvider = ItemType.List.map(t => t.PtItemType);
|
||||
public statusesProvider = PT_ITEM_STATUSES;
|
||||
public prioritiesProvider = PT_ITEM_PRIORITIES;
|
||||
const showAddModal = ref(false);
|
||||
const users = ref<PtUser[]>([]);
|
||||
const itemForm = ref<PtItemDetailsEditFormModel | undefined>();
|
||||
const selectedAssignee = ref<PtUser | undefined>();
|
||||
let { item } = toRefs(props);
|
||||
|
||||
public showAddModal: boolean = false;
|
||||
public users: PtUser[] = [];
|
||||
public itemForm: PtItemDetailsEditFormModel | undefined;
|
||||
public selectedAssignee: PtUser | undefined;
|
||||
if (props.item) {
|
||||
itemForm.value = ptItemToFormModel(props.item);
|
||||
selectedAssignee.value = item.value.assignee as PtUser;
|
||||
}
|
||||
|
||||
@Emit('usersRequested')
|
||||
public usersRequested() {}
|
||||
@Emit('itemSaved')
|
||||
public itemSaved(item: PtItem): void {}
|
||||
|
||||
public created() {
|
||||
if (this.item) {
|
||||
this.itemForm = ptItemToFormModel(this.item);
|
||||
this.selectedAssignee = this.item.assignee;
|
||||
const assigneePickerOpen = () => {
|
||||
props.usersObs.subscribe((newUsers: PtUser[]) => {
|
||||
if (newUsers.length > 0) {
|
||||
users.value = newUsers;
|
||||
showAddModal.value = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public assigneePickerOpen() {
|
||||
this.usersObs.subscribe((users: PtUser[]) => {
|
||||
if (users.length > 0) {
|
||||
this.users = users;
|
||||
this.showAddModal = true;
|
||||
}
|
||||
});
|
||||
context.emit("usersRequested");
|
||||
};
|
||||
|
||||
this.usersRequested();
|
||||
}
|
||||
const onNonTextFieldChange = () => {
|
||||
notifyUpdateItem();
|
||||
};
|
||||
|
||||
public onNonTextFieldChange() {
|
||||
this.notifyUpdateItem();
|
||||
}
|
||||
const onBlurTextField = () => {
|
||||
notifyUpdateItem();
|
||||
};
|
||||
|
||||
public onBlurTextField() {
|
||||
this.notifyUpdateItem();
|
||||
}
|
||||
const toggleModal = () => {
|
||||
showAddModal.value = !showAddModal.value;
|
||||
return false;
|
||||
};
|
||||
|
||||
private toggleModal() {
|
||||
this.showAddModal = !this.showAddModal;
|
||||
return false;
|
||||
}
|
||||
const assigneePickerClose = (user: PtUser) => {
|
||||
selectedAssignee.value = user;
|
||||
itemForm.value!.assigneeName = user.fullName;
|
||||
notifyUpdateItem();
|
||||
showAddModal.value = false;
|
||||
};
|
||||
|
||||
private assigneePickerClose(user: PtUser) {
|
||||
this.selectedAssignee = user;
|
||||
this.itemForm!.assigneeName = user.fullName;
|
||||
this.notifyUpdateItem();
|
||||
this.showAddModal = false;
|
||||
}
|
||||
const notifyUpdateItem = () => {
|
||||
if (!itemForm.value) {
|
||||
return;
|
||||
}
|
||||
const updatedItem = getUpdatedItem(
|
||||
props.item!,
|
||||
itemForm.value,
|
||||
selectedAssignee.value!
|
||||
);
|
||||
context.emit('itemSaved', updatedItem);
|
||||
};
|
||||
|
||||
private notifyUpdateItem() {
|
||||
if (!this.itemForm) {
|
||||
return;
|
||||
}
|
||||
const updatedItem = this.getUpdatedItem(
|
||||
this.item!,
|
||||
this.itemForm,
|
||||
this.selectedAssignee!
|
||||
);
|
||||
this.itemSaved(updatedItem);
|
||||
}
|
||||
const getUpdatedItem = (
|
||||
item: PtItem,
|
||||
itemForm: PtItemDetailsEditFormModel,
|
||||
assignee: PtUser
|
||||
): PtItem => {
|
||||
const updatedItem = Object.assign({}, item, {
|
||||
title: itemForm.title,
|
||||
description: itemForm.description,
|
||||
type: itemForm.typeStr,
|
||||
status: itemForm.statusStr,
|
||||
priority: itemForm.priorityStr,
|
||||
estimate: itemForm.estimate,
|
||||
assignee,
|
||||
});
|
||||
|
||||
private getUpdatedItem(
|
||||
item: PtItem,
|
||||
itemForm: PtItemDetailsEditFormModel,
|
||||
assignee: PtUser
|
||||
): PtItem {
|
||||
const updatedItem = Object.assign({}, item, {
|
||||
title: itemForm.title,
|
||||
description: itemForm.description,
|
||||
type: itemForm.typeStr,
|
||||
status: itemForm.statusStr,
|
||||
priority: itemForm.priorityStr,
|
||||
estimate: itemForm.estimate,
|
||||
assignee,
|
||||
});
|
||||
return updatedItem;
|
||||
};
|
||||
|
||||
return updatedItem;
|
||||
}
|
||||
}
|
||||
return {
|
||||
assigneePickerClose,
|
||||
toggleModal,
|
||||
onBlurTextField,
|
||||
assigneePickerOpen,
|
||||
onNonTextFieldChange,
|
||||
itemTypesProvider,
|
||||
statusesProvider,
|
||||
prioritiesProvider,
|
||||
showAddModal,
|
||||
users,
|
||||
selectedAssignee,
|
||||
itemForm,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -7,18 +7,18 @@
|
|||
placeholder="Enter new task..."
|
||||
class="form-control pt-text-task-add"
|
||||
name="newTask"
|
||||
>
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
@click="() => onAddTapped()"
|
||||
class="btn btn-primary"
|
||||
:disabled="!newTaskTitle"
|
||||
>Add</button>
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<hr />
|
||||
<div v-for="task in tasks" :key="task.id" class="input-group mb-3 col-sm-6">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
|
@ -49,72 +49,80 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop, Emit, Watch } from 'vue-property-decorator';
|
||||
import { EMPTY_STRING } from '@/core/helpers/string-helpers';
|
||||
import { PtTask } from '@/core/models/domain';
|
||||
import { PtNewTask } from '@/shared/models/dto/pt-new-task';
|
||||
import { PtTaskUpdate } from '@/shared/models/dto/pt-task-update';
|
||||
import { defineComponent, PropType, ref } from "vue";
|
||||
import { EMPTY_STRING } from "@/core/helpers/string-helpers";
|
||||
import { PtTask } from "@/core/models/domain";
|
||||
import { PtNewTask } from "@/shared/models/dto/pt-new-task";
|
||||
import { PtTaskUpdate } from "@/shared/models/dto/pt-task-update";
|
||||
|
||||
@Component
|
||||
export default class PtItemTasks extends Vue {
|
||||
@Prop() public tasks!: PtTask[];
|
||||
export default defineComponent({
|
||||
name: "PtItemTasks",
|
||||
props: {
|
||||
tasks: Array as PropType<PtTask[]>
|
||||
},
|
||||
setup(props, context) {
|
||||
const newTaskTitle = ref(EMPTY_STRING);
|
||||
let lastUpdatedTitle = EMPTY_STRING;
|
||||
|
||||
public newTaskTitle = EMPTY_STRING;
|
||||
private lastUpdatedTitle = EMPTY_STRING;
|
||||
@Emit('addNewTask')
|
||||
public addNewTask(newTask: PtNewTask) {}
|
||||
@Emit('updateTask')
|
||||
public updateTask(taskUpdate: PtTaskUpdate) {}
|
||||
const onAddTapped = () => {
|
||||
const newTitle = newTaskTitle.value.trim();
|
||||
if (newTitle.length === 0) {
|
||||
return;
|
||||
}
|
||||
const newTask: PtNewTask = {
|
||||
title: newTitle,
|
||||
completed: false,
|
||||
};
|
||||
context.emit("addNewTask", newTask);
|
||||
|
||||
public onAddTapped() {
|
||||
const newTitle = this.newTaskTitle.trim();
|
||||
if (newTitle.length === 0) {
|
||||
return;
|
||||
}
|
||||
const newTask: PtNewTask = {
|
||||
title: newTitle,
|
||||
completed: false,
|
||||
};
|
||||
this.addNewTask(newTask);
|
||||
newTaskTitle.value = EMPTY_STRING;
|
||||
};
|
||||
|
||||
this.newTaskTitle = EMPTY_STRING;
|
||||
}
|
||||
const onToggleTapped = (task: PtTask) => {
|
||||
const taskUpdate: PtTaskUpdate = {
|
||||
task,
|
||||
toggle: true,
|
||||
};
|
||||
context.emit("updateTask", taskUpdate);
|
||||
};
|
||||
|
||||
public onToggleTapped(task: PtTask) {
|
||||
const taskUpdate: PtTaskUpdate = {
|
||||
task,
|
||||
toggle: true,
|
||||
};
|
||||
this.updateTask(taskUpdate);
|
||||
}
|
||||
const onTaskTitleChange = (task: PtTask, event: any) => {
|
||||
if (task.title === event.target.value) {
|
||||
return;
|
||||
}
|
||||
lastUpdatedTitle = event.target.value;
|
||||
};
|
||||
|
||||
public onTaskTitleChange(task: PtTask, event: any) {
|
||||
if (task.title === event.target.value) {
|
||||
return;
|
||||
}
|
||||
this.lastUpdatedTitle = event.target.value;
|
||||
}
|
||||
const onTaskBlurred = (task: PtTask) => {
|
||||
if (task.title === lastUpdatedTitle) {
|
||||
return;
|
||||
}
|
||||
const taskUpdate: PtTaskUpdate = {
|
||||
task,
|
||||
toggle: false,
|
||||
newTitle: lastUpdatedTitle,
|
||||
};
|
||||
context.emit("updateTask", taskUpdate);
|
||||
lastUpdatedTitle = EMPTY_STRING;
|
||||
};
|
||||
|
||||
public onTaskBlurred(task: PtTask) {
|
||||
if (task.title === this.lastUpdatedTitle) {
|
||||
return;
|
||||
}
|
||||
const taskUpdate: PtTaskUpdate = {
|
||||
task,
|
||||
toggle: false,
|
||||
newTitle: this.lastUpdatedTitle,
|
||||
};
|
||||
this.updateTask(taskUpdate);
|
||||
this.lastUpdatedTitle = EMPTY_STRING;
|
||||
}
|
||||
const onTaskDelete = (task: PtTask) => {
|
||||
const taskUpdate: PtTaskUpdate = {
|
||||
task,
|
||||
toggle: false,
|
||||
delete: true,
|
||||
};
|
||||
context.emit("updateTask", taskUpdate);
|
||||
};
|
||||
|
||||
public onTaskDelete(task: PtTask) {
|
||||
const taskUpdate: PtTaskUpdate = {
|
||||
task,
|
||||
toggle: false,
|
||||
delete: true,
|
||||
};
|
||||
this.updateTask(taskUpdate);
|
||||
}
|
||||
}
|
||||
return {
|
||||
onTaskDelete,
|
||||
onTaskBlurred,
|
||||
onTaskTitleChange,
|
||||
onToggleTapped,
|
||||
onAddTapped,
|
||||
newTaskTitle,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export const CONFIG = {
|
||||
apiEndpoint: 'http://localhost:8080/api/',
|
||||
apiEndpoint: 'http://localhost:8083/api/',
|
||||
};
|
||||
|
|
22
src/main.ts
|
@ -1,18 +1,6 @@
|
|||
import Vue from 'vue';
|
||||
import App from './App.vue';
|
||||
import router from './router';
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import "bootstrap/dist/css/bootstrap.css";
|
||||
|
||||
import BootstrapVue from 'bootstrap-vue';
|
||||
|
||||
Vue.use(BootstrapVue);
|
||||
|
||||
import 'bootstrap/dist/css/bootstrap.css';
|
||||
import 'bootstrap-vue/dist/bootstrap-vue.css';
|
||||
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
render: (h) => h(App),
|
||||
}).$mount('#app');
|
||||
createApp(App).use(router).mount("#app");
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import Router from 'vue-router';
|
||||
import DashboardPage from '@/views/DashboardPage.vue';
|
||||
import BacklogPage from '@/views/BacklogPage.vue';
|
||||
import DetailPage from '@/views/DetailPage.vue';
|
||||
|
||||
Vue.use(Router);
|
||||
|
||||
export default new Router({
|
||||
mode: 'history',
|
||||
base: process.env.BASE_URL,
|
||||
routes: [
|
||||
{
|
||||
path: '',
|
||||
redirect: '/dashboard',
|
||||
},
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'dashboard',
|
||||
component: DashboardPage,
|
||||
},
|
||||
{
|
||||
path: '/backlog',
|
||||
redirect: '/backlog/open',
|
||||
},
|
||||
{
|
||||
path: '/backlog/:preset',
|
||||
name: 'backlog',
|
||||
component: BacklogPage,
|
||||
},
|
||||
{
|
||||
path: '/detail/:id',
|
||||
redirect: '/detail/:id/details',
|
||||
},
|
||||
{
|
||||
path: '/detail/:id/:screen',
|
||||
name: 'detail',
|
||||
component: DetailPage,
|
||||
},
|
||||
],
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
|
||||
import DashboardPage from '@/views/DashboardPage.vue';
|
||||
import BacklogPage from '@/views/BacklogPage.vue';
|
||||
import DetailPage from '@/views/DetailPage.vue';
|
||||
|
||||
|
||||
const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/dashboard',
|
||||
},
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'dashboard',
|
||||
component: DashboardPage,
|
||||
},
|
||||
{
|
||||
path: '/backlog',
|
||||
redirect: '/backlog/open',
|
||||
},
|
||||
{
|
||||
path: '/backlog/:preset',
|
||||
name: 'backlog',
|
||||
component: BacklogPage,
|
||||
},
|
||||
{
|
||||
path: '/detail/:id',
|
||||
redirect: '/detail/:id/details',
|
||||
},
|
||||
{
|
||||
path: '/detail/:id/:screen',
|
||||
name: 'detail',
|
||||
component: DetailPage,
|
||||
},
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(process.env.BASE_URL),
|
||||
routes,
|
||||
});
|
||||
|
||||
export default router;
|
|
@ -1,25 +1,25 @@
|
|||
import { Store } from '@/core/state/app-store';
|
||||
import { BacklogRepository } from '@/repositories/backlog-repository';
|
||||
import { Store } from "@/core/state/app-store";
|
||||
import { BacklogRepository } from "@/repositories/backlog-repository";
|
||||
|
||||
import { PtItem, PtUser, PtTask, PtComment } from '@/core/models/domain';
|
||||
import { PtItem, PtUser, PtTask, PtComment } from "@/core/models/domain";
|
||||
|
||||
import { PriorityEnum, StatusEnum } from '@/core/models/domain/enums';
|
||||
import { getUserAvatarUrl } from '@/core/helpers/user-avatar-helper';
|
||||
import { PriorityEnum, StatusEnum } from "@/core/models/domain/enums";
|
||||
import { getUserAvatarUrl } from "@/core/helpers/user-avatar-helper";
|
||||
|
||||
|
||||
import { CONFIG } from '@/config';
|
||||
import { PresetType } from '@/core/models/domain/types';
|
||||
import { datesForTask, datesForPtItem, datesForComment } from '@/core/helpers/date-utils';
|
||||
import { PtNewItem } from '@/shared/models/dto/pt-new-item';
|
||||
import { PtNewTask } from '@/shared/models/dto/pt-new-task';
|
||||
import { PtNewComment } from '@/shared/models/dto/pt-new-comment';
|
||||
import { CONFIG } from "@/config";
|
||||
import { PresetType } from "@/core/models/domain/types";
|
||||
import { datesForTask, datesForPtItem, datesForComment } from "@/core/helpers/date-utils";
|
||||
import { PtNewItem } from "@/shared/models/dto/pt-new-item";
|
||||
import { PtNewTask } from "@/shared/models/dto/pt-new-task";
|
||||
import { PtNewComment } from "@/shared/models/dto/pt-new-comment";
|
||||
|
||||
|
||||
export const tempCurrentUser = {
|
||||
avatar: getUserAvatarUrl(CONFIG.apiEndpoint, 21),
|
||||
dateCreated: new Date(),
|
||||
dateModified: new Date(),
|
||||
fullName: 'Alex Ziskind',
|
||||
fullName: "Alex Ziskind",
|
||||
id: 21,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
import Vue, { VNode } from 'vue';
|
||||
|
||||
declare global {
|
||||
namespace JSX {
|
||||
// tslint:disable no-empty-interface
|
||||
interface Element extends VNode {}
|
||||
// tslint:disable no-empty-interface
|
||||
interface ElementClass extends Vue {}
|
||||
interface IntrinsicElements {
|
||||
[elem: string]: any;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
/* eslint-disable */
|
||||
declare module '*.vue' {
|
||||
import Vue from 'vue';
|
||||
export default Vue;
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
|
|
|
@ -106,160 +106,164 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Model, Watch } from 'vue-property-decorator';
|
||||
import { Route } from 'vue-router';
|
||||
import { defineComponent, ref } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
import { BacklogService } from "@/services/backlog-service";
|
||||
import { BacklogRepository } from "@/repositories/backlog-repository";
|
||||
import { EMPTY_STRING } from "@/core/helpers";
|
||||
import { Store } from "@/core/state/app-store";
|
||||
|
||||
import { BacklogService } from '@/services/backlog-service';
|
||||
import { BacklogRepository } from '@/repositories/backlog-repository';
|
||||
import { EMPTY_STRING } from '@/core/helpers';
|
||||
import { Store } from '@/core/state/app-store';
|
||||
import { PresetType } from "@/core/models/domain/types";
|
||||
import { PtItem, PtUser } from "@/core/models/domain";
|
||||
import { ItemType } from "@/core/constants";
|
||||
import { PtNewItem } from "@/shared/models/dto/pt-new-item";
|
||||
import PresetFilter from "@/components/PresetFilter.vue";
|
||||
import { getIndicatorClass } from "@/shared/helpers/priority-styling";
|
||||
|
||||
import { PresetType } from '@/core/models/domain/types';
|
||||
import { PtItem, PtUser } from '@/core/models/domain';
|
||||
import { ItemType } from '@/core/constants';
|
||||
import { PtNewItem } from '@/shared/models/dto/pt-new-item';
|
||||
import PresetFilter from '@/components/PresetFilter.vue';
|
||||
import { getIndicatorClass } from '@/shared/helpers/priority-styling';
|
||||
export default defineComponent({
|
||||
name: "BacklogPage",
|
||||
components: {
|
||||
PresetFilter,
|
||||
},
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const currentPreset = ref<PresetType>("open");
|
||||
const items = ref<PtItem[]>([]);
|
||||
const itemTypesProvider = ref(ItemType.List.map(t => t.PtItemType));
|
||||
const showAddModal = ref(false);
|
||||
const listItemTap = (item: PtItem) => {
|
||||
// navigate to detail page
|
||||
router.push(`/detail/${item.id}`);
|
||||
};
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
PresetFilter,
|
||||
},
|
||||
})
|
||||
export default class BacklogPage extends Vue {
|
||||
public currentPreset: PresetType;
|
||||
public items: PtItem[];
|
||||
public itemTypesProvider = ItemType.List.map(t => t.PtItemType);
|
||||
public showAddModal: boolean;
|
||||
public newItem: PtNewItem;
|
||||
private store: Store = new Store();
|
||||
private backlogRepo: BacklogRepository = new BacklogRepository();
|
||||
private backlogService: BacklogService = new BacklogService(
|
||||
this.backlogRepo,
|
||||
this.store
|
||||
);
|
||||
const getIndicatorImage = (item: PtItem) => {
|
||||
return ItemType.imageResFromType(item.type);
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const getPriorityClass = (item: PtItem): string => {
|
||||
const indicatorClass = getIndicatorClass(item.priority);
|
||||
return indicatorClass;
|
||||
};
|
||||
|
||||
this.currentPreset = 'open';
|
||||
this.items = [];
|
||||
this.showAddModal = false;
|
||||
this.newItem = this.initModalNewItem();
|
||||
}
|
||||
const onAddSave = () => {
|
||||
if (store.value.currentUser) {
|
||||
backlogService
|
||||
.addNewPtItem(newItem.value, store.value.currentUser)
|
||||
.then((nextItem: PtItem) => {
|
||||
showAddModal.value = false;
|
||||
newItem.value = initModalNewItem();
|
||||
items.value = [nextItem, ...items.value];
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@Watch('$route')
|
||||
public onRouteChange(val: Route, oldVal: Route) {
|
||||
this.refresh();
|
||||
}
|
||||
const refresh = () => {
|
||||
backlogService.getItems(currentPreset.value).then(ptItems => {
|
||||
items.value = ptItems;
|
||||
});
|
||||
};
|
||||
|
||||
public created() {
|
||||
this.currentPreset = this.$route.params.preset as PresetType;
|
||||
this.refresh();
|
||||
}
|
||||
const onSelectPresetTap = (preset: PresetType) => {
|
||||
currentPreset.value = preset;
|
||||
router.push('/backlog/' + preset);
|
||||
};
|
||||
|
||||
public listItemTap(item: PtItem) {
|
||||
// navigate to detail page
|
||||
this.$router.push(`/detail/${item.id}`);
|
||||
}
|
||||
const toggleModal = () => {
|
||||
showAddModal.value = !showAddModal.value;
|
||||
};
|
||||
|
||||
public getIndicatorImage(item: PtItem) {
|
||||
return ItemType.imageResFromType(item.type);
|
||||
}
|
||||
const initModalNewItem = (): PtNewItem => {
|
||||
return {
|
||||
title: EMPTY_STRING,
|
||||
description: EMPTY_STRING,
|
||||
typeStr: 'PBI',
|
||||
};
|
||||
};
|
||||
|
||||
public getPriorityClass(item: PtItem): string {
|
||||
const indicatorClass = getIndicatorClass(item.priority);
|
||||
return indicatorClass;
|
||||
}
|
||||
// const getItemTypeCellMarkup = (item: PtItem) => {
|
||||
// return `<img src="${getIndicatorImage(
|
||||
// item
|
||||
// )}" class="backlog-icon" />`;
|
||||
// };
|
||||
|
||||
public onAddSave() {
|
||||
if (this.store.value.currentUser) {
|
||||
this.backlogService
|
||||
.addNewPtItem(this.newItem, this.store.value.currentUser)
|
||||
.then((nextItem: PtItem) => {
|
||||
this.showAddModal = false;
|
||||
this.newItem = this.initModalNewItem();
|
||||
this.items = [nextItem, ...this.items];
|
||||
});
|
||||
}
|
||||
}
|
||||
// const getAssigneeCellMarkup = (user: PtUser) => {
|
||||
// return `
|
||||
// <div>
|
||||
// <img src="${user.avatar}" class="li-avatar rounded mx-auto" />
|
||||
// <span style="margin-left: 10px;">${user.fullName}</span>
|
||||
// </div>
|
||||
// `;
|
||||
// }
|
||||
|
||||
private refresh() {
|
||||
this.backlogService.getItems(this.currentPreset).then(ptItems => {
|
||||
this.items = ptItems;
|
||||
});
|
||||
}
|
||||
// const getPriorityCellMarkup = (item: PtItem) => {
|
||||
// return `<span class="${'badge ' + getPriorityClass(item)}">${
|
||||
// item.priority
|
||||
// }</span>`;
|
||||
// }
|
||||
|
||||
private onSelectPresetTap(preset: PresetType) {
|
||||
this.currentPreset = preset;
|
||||
this.$router.push('/backlog/' + preset);
|
||||
}
|
||||
// const getCreatedDateCellMarkup = (item: PtItem) => {
|
||||
// return `<span class="li-date">${item.dateCreated.toDateString()}</span>`;
|
||||
// }
|
||||
const newItem = ref<PtNewItem>(initModalNewItem());
|
||||
let store: Store = new Store();
|
||||
let backlogRepo: BacklogRepository = new BacklogRepository();
|
||||
let backlogService: BacklogService = new BacklogService(backlogRepo, store);
|
||||
|
||||
private toggleModal() {
|
||||
this.showAddModal = !this.showAddModal;
|
||||
}
|
||||
// @Watch('$route')
|
||||
// public onRouteChange(val: Route, oldVal: Route) {
|
||||
// refresh();
|
||||
// }
|
||||
|
||||
private initModalNewItem(): PtNewItem {
|
||||
return {
|
||||
title: EMPTY_STRING,
|
||||
description: EMPTY_STRING,
|
||||
typeStr: 'PBI',
|
||||
};
|
||||
}
|
||||
// public created() {
|
||||
// currentPreset = $route.params.preset as PresetType;
|
||||
// refresh();
|
||||
// }
|
||||
|
||||
private getItemTypeCellMarkup(item: PtItem) {
|
||||
return `<img src="${this.getIndicatorImage(
|
||||
item
|
||||
)}" class="backlog-icon" />`;
|
||||
}
|
||||
|
||||
private getAssigneeCellMarkup(user: PtUser) {
|
||||
return `
|
||||
<div>
|
||||
<img src="${user.avatar}" class="li-avatar rounded mx-auto" />
|
||||
<span style="margin-left: 10px;">${user.fullName}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private getPriorityCellMarkup(item: PtItem) {
|
||||
return `<span class="${'badge ' + this.getPriorityClass(item)}">${
|
||||
item.priority
|
||||
}</span>`;
|
||||
}
|
||||
|
||||
private getCreatedDateCellMarkup(item: PtItem) {
|
||||
return `<span class="li-date">${item.dateCreated.toDateString()}</span>`;
|
||||
}
|
||||
}
|
||||
return {
|
||||
onAddSave,
|
||||
toggleModal,
|
||||
getPriorityClass,
|
||||
getIndicatorImage,
|
||||
listItemTap,
|
||||
currentPreset,
|
||||
itemTypesProvider,
|
||||
newItem,
|
||||
onSelectPresetTap,
|
||||
refresh,
|
||||
items,
|
||||
showAddModal,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.backlog-icon {
|
||||
height: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.li-indicator {
|
||||
height: 58px;
|
||||
width: 10px;
|
||||
text-align: left;
|
||||
height: 58px;
|
||||
width: 10px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.li-indicator div {
|
||||
width: 5px;
|
||||
height: 58px;
|
||||
width: 5px;
|
||||
height: 58px;
|
||||
}
|
||||
|
||||
.li-info-wrapper {
|
||||
margin-left: 5px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.li-title {
|
||||
font-size: 14px;
|
||||
color: #4b5833;
|
||||
font-size: 14px;
|
||||
color: #4b5833;
|
||||
}
|
||||
|
||||
.pt-table-row {
|
||||
cursor: pointer;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
<h2>
|
||||
<span class="small text-uppercase text-muted d-block">Statistics</span>
|
||||
<span
|
||||
v-if="this.filter.dateStart && this.filter.dateEnd"
|
||||
>{{formatDateEnUs(this.filter.dateStart)}} - {{formatDateEnUs(this.filter.dateEnd)}}</span>
|
||||
v-if="filter.dateStart && filter.dateEnd"
|
||||
>{{formatDateEnUs(filter.dateStart)}} - {{formatDateEnUs(filter.dateEnd)}}</span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
|
@ -49,81 +49,86 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import {
|
||||
DashboardRepository,
|
||||
DashboardFilter,
|
||||
FilteredIssues,
|
||||
} from '@/repositories/dashboard-repository';
|
||||
import { DashboardService } from '@/services/dashboard-service';
|
||||
DashboardRepository,
|
||||
DashboardFilter,
|
||||
FilteredIssues,
|
||||
} from "@/repositories/dashboard-repository";
|
||||
import { DashboardService } from "@/services/dashboard-service";
|
||||
import {
|
||||
TypeCounts,
|
||||
PriorityCounts,
|
||||
StatusCounts,
|
||||
} from '@/shared/models/ui/stats';
|
||||
import { formatDateEnUs } from '@/core/helpers/date-utils';
|
||||
import ActiveIssues from '@/components/dashboard/ActiveIssues.vue';
|
||||
} from "@/shared/models/ui/stats";
|
||||
import { formatDateEnUs } from "@/core/helpers/date-utils";
|
||||
import ActiveIssues from "@/components/dashboard/ActiveIssues.vue";
|
||||
|
||||
interface DateRange {
|
||||
dateStart: Date;
|
||||
dateEnd: Date;
|
||||
dateStart: Date;
|
||||
dateEnd: Date;
|
||||
}
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
ActiveIssues,
|
||||
},
|
||||
})
|
||||
export default class DashboardPage extends Vue {
|
||||
public filter: DashboardFilter = {};
|
||||
public statusCounts: StatusCounts = {
|
||||
activeItemsCount: 0,
|
||||
closeRate: 0,
|
||||
closedItemsCount: 0,
|
||||
openItemsCount: 0,
|
||||
};
|
||||
public categories: Date[] = [];
|
||||
public itemsOpenByMonth: number[] = [];
|
||||
public itemsClosedByMonth: number[] = [];
|
||||
import { defineComponent, ref } from "vue";
|
||||
|
||||
private dashboardRepo: DashboardRepository = new DashboardRepository();
|
||||
private dashboardService: DashboardService = new DashboardService(
|
||||
this.dashboardRepo
|
||||
export default defineComponent({
|
||||
name: "DashboardPage",
|
||||
components: {
|
||||
ActiveIssues,
|
||||
},
|
||||
setup() {
|
||||
const filter = ref<DashboardFilter>({});
|
||||
const statusCounts = ref<StatusCounts>({
|
||||
activeItemsCount: 0,
|
||||
closeRate: 0,
|
||||
closedItemsCount: 0,
|
||||
openItemsCount: 0,
|
||||
});
|
||||
const categories = ref<Date[]>([]);
|
||||
const itemsOpenByMonth = ref<number[]>([]);
|
||||
const itemsClosedByMonth = ref<number[]>([]);
|
||||
|
||||
const dashboardRepo: DashboardRepository = new DashboardRepository();
|
||||
const dashboardService: DashboardService = new DashboardService(
|
||||
dashboardRepo
|
||||
);
|
||||
|
||||
public created() {
|
||||
this.refresh();
|
||||
}
|
||||
const refresh = () => {
|
||||
dashboardService.getStatusCounts(filter.value as DashboardFilter).then((result) => {
|
||||
statusCounts.value = result;
|
||||
});
|
||||
};
|
||||
refresh();
|
||||
|
||||
private refresh() {
|
||||
this.dashboardService.getStatusCounts(this.filter).then(result => {
|
||||
this.statusCounts = result;
|
||||
});
|
||||
}
|
||||
const onMonthRangeTap = (months: number) => {
|
||||
const range = getDateRange(months);
|
||||
filter.value = {
|
||||
userId: filter.value.userId,
|
||||
dateEnd: range.dateEnd,
|
||||
dateStart: range.dateStart,
|
||||
};
|
||||
refresh();
|
||||
};
|
||||
|
||||
private onMonthRangeTap(months: number) {
|
||||
const range = this.getDateRange(months);
|
||||
this.filter = {
|
||||
userId: this.filter.userId,
|
||||
dateEnd: range.dateEnd,
|
||||
dateStart: range.dateStart,
|
||||
};
|
||||
this.refresh();
|
||||
}
|
||||
const getDateRange = (months: number): DateRange => {
|
||||
const now = new Date();
|
||||
const start = new Date();
|
||||
start.setMonth(start.getMonth() - months);
|
||||
return {
|
||||
dateStart: start,
|
||||
dateEnd: now,
|
||||
};
|
||||
};
|
||||
|
||||
private getDateRange(months: number): DateRange {
|
||||
const now = new Date();
|
||||
const start = new Date();
|
||||
start.setMonth(start.getMonth() - months);
|
||||
return {
|
||||
dateStart: start,
|
||||
dateEnd: now,
|
||||
};
|
||||
}
|
||||
|
||||
private formatDateEnUs(date: Date) {
|
||||
return formatDateEnUs(date);
|
||||
}
|
||||
}
|
||||
return {
|
||||
formatDateEnUs,
|
||||
onMonthRangeTap,
|
||||
refresh,
|
||||
filter,
|
||||
statusCounts,
|
||||
categories,
|
||||
itemsOpenByMonth,
|
||||
itemsClosedByMonth,
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -51,11 +51,9 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Model, Watch } from 'vue-property-decorator';
|
||||
import { Route } from 'vue-router';
|
||||
|
||||
import { defineComponent, ref } from "vue";
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { BacklogService } from '@/services/backlog-service';
|
||||
|
@ -78,136 +76,137 @@ import { PtNewTask } from '@/shared/models/dto/pt-new-task';
|
|||
import { PtTaskUpdate } from '@/shared/models/dto/pt-task-update';
|
||||
import { PtNewComment } from '@/shared/models/dto/pt-new-comment';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
PtItemDetails,
|
||||
PtItemTasks,
|
||||
PtItemChitchat,
|
||||
},
|
||||
})
|
||||
export default class DetailPage extends Vue {
|
||||
public selectedDetailsScreen: DetailScreenType = 'details';
|
||||
private store: Store = new Store();
|
||||
private backlogRepo: BacklogRepository = new BacklogRepository();
|
||||
private backlogService: BacklogService = new BacklogService(
|
||||
this.backlogRepo,
|
||||
this.store
|
||||
);
|
||||
private ptUserService: PtUserService = new PtUserService(this.store);
|
||||
export default defineComponent({
|
||||
name: "DetailPage",
|
||||
components: {
|
||||
PtItemDetails,
|
||||
PtItemTasks,
|
||||
PtItemChitchat,
|
||||
},
|
||||
setup() {
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const selectedDetailsScreen = ref<DetailScreenType>('details' );
|
||||
let store: Store = new Store();
|
||||
let backlogRepo: BacklogRepository = new BacklogRepository();
|
||||
let backlogService: BacklogService = new BacklogService(backlogRepo, store);
|
||||
let ptUserService: PtUserService = new PtUserService(store);
|
||||
let itemId = 0;
|
||||
const item = ref<PtItem | null>();
|
||||
let currentUser = ref<PtUser | undefined>(store.value.currentUser);
|
||||
let users$ = ref<Observable<PtUser[]>>(store.select<PtUser[]>('users'));
|
||||
|
||||
private itemId: number = 0;
|
||||
private item: PtItem | null = null;
|
||||
private currentUser: PtUser | undefined = this.store.value.currentUser;
|
||||
private users$: Observable<PtUser[]> = this.store.select<PtUser[]>('users');
|
||||
selectedDetailsScreen.value = route.params.screen as DetailScreenType;
|
||||
itemId = Number(route.params.id);
|
||||
const refresh = () => {
|
||||
backlogService.getPtItem(itemId).then((newItem) => {
|
||||
item.value = newItem;
|
||||
});
|
||||
};
|
||||
refresh();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
const onScreenSelected = (screen: DetailScreenType) => {
|
||||
selectedDetailsScreen.value = screen;
|
||||
router.push(`/detail/${itemId}/${screen}`);
|
||||
};
|
||||
|
||||
public created() {
|
||||
this.selectedDetailsScreen = this.$route.params
|
||||
.screen as DetailScreenType;
|
||||
this.itemId = Number(this.$route.params.id);
|
||||
this.refresh();
|
||||
}
|
||||
const onItemSaved = (currentItem: PtItem) => {
|
||||
backlogService.updatePtItem(currentItem).then((updateItem: PtItem) => {
|
||||
item.value = updateItem;
|
||||
});
|
||||
};
|
||||
|
||||
public onScreenSelected(screen: DetailScreenType) {
|
||||
this.selectedDetailsScreen = screen;
|
||||
this.$router.push(`/detail/${this.itemId}/${screen}`);
|
||||
}
|
||||
|
||||
public onItemSaved(item: PtItem) {
|
||||
this.backlogService.updatePtItem(item).then((updateItem: PtItem) => {
|
||||
this.item = updateItem;
|
||||
const onAddNewTask = (newTask: PtNewTask) => {
|
||||
if (item.value) {
|
||||
backlogService.addNewPtTask(newTask, item.value).then((nextTask) => {
|
||||
item.value!.tasks = [nextTask].concat(item.value!.tasks);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public onAddNewTask(newTask: PtNewTask) {
|
||||
if (this.item) {
|
||||
this.backlogService
|
||||
.addNewPtTask(newTask, this.item)
|
||||
.then(nextTask => {
|
||||
this.item!.tasks = [nextTask].concat(this.item!.tasks);
|
||||
const onUpdateTask = (taskUpdate: PtTaskUpdate) => {
|
||||
if (item.value) {
|
||||
if (taskUpdate.delete) {
|
||||
backlogService
|
||||
.deletePtTask(item.value, taskUpdate.task)
|
||||
.then(ok => {
|
||||
if (ok) {
|
||||
const newTasks = item.value!.tasks.filter((task) => {
|
||||
if (task.id !== taskUpdate.task.id) {
|
||||
return task;
|
||||
}
|
||||
});
|
||||
item.value!.tasks = newTasks;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
backlogService
|
||||
.updatePtTask(
|
||||
item.value,
|
||||
taskUpdate.task,
|
||||
taskUpdate.toggle,
|
||||
taskUpdate.newTitle
|
||||
)
|
||||
.then((updatedTask) => {
|
||||
const newTasks = item.value!.tasks.map((task) => {
|
||||
if (task.id === updatedTask.id) {
|
||||
return updatedTask;
|
||||
} else {
|
||||
return task;
|
||||
}
|
||||
});
|
||||
item.value!.tasks = newTasks;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onAddNewComment = (newComment: PtNewComment) => {
|
||||
if (item.value) {
|
||||
backlogService
|
||||
.addNewPtComment(newComment, item.value)
|
||||
.then(nextComment => {
|
||||
item.value!.comments = [nextComment].concat(item.value!.comments);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onUsersRequested = () => {
|
||||
ptUserService.fetchUsers();
|
||||
};
|
||||
|
||||
const getIndicatorImage = (currentItem: PtItem) => {
|
||||
return ItemType.imageResFromType(currentItem.type);
|
||||
};
|
||||
|
||||
const getPriorityClass = (currentItem: PtItem): string => {
|
||||
const indicatorClass = getIndicatorClass(currentItem.priority);
|
||||
return indicatorClass;
|
||||
};
|
||||
|
||||
const initModalNewItem = (): PtNewItem => {
|
||||
return {
|
||||
title: EMPTY_STRING,
|
||||
description: EMPTY_STRING,
|
||||
typeStr: 'PBI',
|
||||
};
|
||||
}
|
||||
|
||||
public onUpdateTask(taskUpdate: PtTaskUpdate) {
|
||||
if (this.item) {
|
||||
if (taskUpdate.delete) {
|
||||
this.backlogService
|
||||
.deletePtTask(this.item, taskUpdate.task)
|
||||
.then(ok => {
|
||||
if (ok) {
|
||||
const newTasks = this.item!.tasks.filter(task => {
|
||||
if (task.id !== taskUpdate.task.id) {
|
||||
return task;
|
||||
}
|
||||
});
|
||||
this.item!.tasks = newTasks;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.backlogService
|
||||
.updatePtTask(
|
||||
this.item,
|
||||
taskUpdate.task,
|
||||
taskUpdate.toggle,
|
||||
taskUpdate.newTitle
|
||||
)
|
||||
.then(updatedTask => {
|
||||
const newTasks = this.item!.tasks.map(task => {
|
||||
if (task.id === updatedTask.id) {
|
||||
return updatedTask;
|
||||
} else {
|
||||
return task;
|
||||
}
|
||||
});
|
||||
this.item!.tasks = newTasks;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public onAddNewComment(newComment: PtNewComment) {
|
||||
if (this.item) {
|
||||
this.backlogService
|
||||
.addNewPtComment(newComment, this.item)
|
||||
.then(nextComment => {
|
||||
this.item!.comments = [nextComment].concat(
|
||||
this.item!.comments
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public onUsersRequested() {
|
||||
this.ptUserService.fetchUsers();
|
||||
}
|
||||
|
||||
public getIndicatorImage(item: PtItem) {
|
||||
return ItemType.imageResFromType(item.type);
|
||||
}
|
||||
|
||||
public getPriorityClass(item: PtItem): string {
|
||||
const indicatorClass = getIndicatorClass(item.priority);
|
||||
return indicatorClass;
|
||||
}
|
||||
|
||||
private refresh() {
|
||||
this.backlogService.getPtItem(this.itemId).then(item => {
|
||||
this.item = item;
|
||||
});
|
||||
}
|
||||
|
||||
private initModalNewItem(): PtNewItem {
|
||||
return {
|
||||
title: EMPTY_STRING,
|
||||
description: EMPTY_STRING,
|
||||
typeStr: 'PBI',
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
getPriorityClass,
|
||||
getIndicatorImage,
|
||||
onUsersRequested,
|
||||
onAddNewComment,
|
||||
onUpdateTask,
|
||||
onAddNewTask,
|
||||
onScreenSelected,
|
||||
onItemSaved,
|
||||
item,
|
||||
currentUser,
|
||||
users$,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
import { PluginObject } from 'vue';
|
||||
export declare const VuePlugin: PluginObject<{}>;
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2015",
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"importHelpers": true,
|
||||
"moduleResolution": "node",
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"sourceMap": true,
|
||||
|
|
24
tslint.json
|
@ -1,24 +0,0 @@
|
|||
{
|
||||
"defaultSeverity": "warning",
|
||||
"extends": [
|
||||
"tslint:recommended"
|
||||
],
|
||||
"linterOptions": {
|
||||
"exclude": [
|
||||
"node_modules/**"
|
||||
]
|
||||
},
|
||||
"rules": {
|
||||
"quotemark": [
|
||||
true,
|
||||
"single"
|
||||
],
|
||||
"interface-name": false,
|
||||
"ordered-imports": false,
|
||||
"object-literal-sort-keys": false,
|
||||
"no-consecutive-blank-lines": false,
|
||||
"no-empty": false,
|
||||
"trailing-comma": false,
|
||||
"arrow-parens": false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
lintOnSave: false,
|
||||
};
|