chore: initial update to vue 3 and compositions api

This commit is contained in:
zdravkov 2021-11-04 16:51:13 +02:00
Родитель d7f099aad4
Коммит 7fab538f04
40 изменённых файлов: 4786 добавлений и 2093 удалений

Просмотреть файл

@ -1,3 +1,3 @@
> 1%
last 2 versions
not ie <= 8
not dead

20
.eslintrc.js Normal file
Просмотреть файл

@ -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",
},
};

4
.gitignore поставляемый
Просмотреть файл

@ -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

3
babel.config.js Normal file
Просмотреть файл

@ -0,0 +1,3 @@
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
};

Просмотреть файл

@ -1 +0,0 @@
<YOUR LICENSE>

5396
package-lock.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 -->

Просмотреть файл

@ -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>

Двоичные данные
src/assets/logo.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 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/',
};

Просмотреть файл

@ -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,
},
],
});

42
src/router/index.ts Normal file
Просмотреть файл

@ -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,
};

13
src/shims-tsx.d.ts поставляемый
Просмотреть файл

@ -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;
}
}
}

6
src/shims-vue.d.ts поставляемый
Просмотреть файл

@ -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>

2
src/vue-bootstrap.d.ts поставляемый
Просмотреть файл

@ -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,

Просмотреть файл

@ -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
}
}

3
vue.config.js Normal file
Просмотреть файл

@ -0,0 +1,3 @@
module.exports = {
lintOnSave: false,
};