WIP: ARCHIVE team views #1098
|
@ -37,6 +37,7 @@
|
||||||
"lodash.clonedeep": "4.5.0",
|
"lodash.clonedeep": "4.5.0",
|
||||||
"lodash.debounce": "4.0.8",
|
"lodash.debounce": "4.0.8",
|
||||||
"marked": "4.0.5",
|
"marked": "4.0.5",
|
||||||
|
"pinia": "^2.0.4",
|
||||||
"register-service-worker": "1.7.2",
|
"register-service-worker": "1.7.2",
|
||||||
"snake-case": "3.0.4",
|
"snake-case": "3.0.4",
|
||||||
"ufo": "0.7.9",
|
"ufo": "0.7.9",
|
||||||
|
|
|
@ -33,6 +33,8 @@ import './registerServiceWorker'
|
||||||
|
|
||||||
// Vuex
|
// Vuex
|
||||||
import {store} from './store'
|
import {store} from './store'
|
||||||
|
// Pinia
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
// i18n
|
// i18n
|
||||||
import {i18n} from './i18n'
|
import {i18n} from './i18n'
|
||||||
|
|
||||||
|
@ -133,8 +135,9 @@ if (window.SENTRY_ENABLED) {
|
||||||
import('./sentry').then(sentry => sentry.default(app, router))
|
import('./sentry').then(sentry => sentry.default(app, router))
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use(router)
|
|
||||||
app.use(store)
|
|
||||||
app.use(i18n)
|
app.use(i18n)
|
||||||
|
app.use(store)
|
||||||
|
app.use(createPinia())
|
||||||
|
app.use(router)
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
|
@ -14,8 +14,7 @@ import LinkShareAuthComponent from '../views/sharing/LinkSharingAuth'
|
||||||
import TaskDetailViewModal from '../views/tasks/TaskDetailViewModal'
|
import TaskDetailViewModal from '../views/tasks/TaskDetailViewModal'
|
||||||
import TaskDetailView from '../views/tasks/TaskDetailView'
|
import TaskDetailView from '../views/tasks/TaskDetailView'
|
||||||
import ListNamespaces from '../views/namespaces/ListNamespaces'
|
import ListNamespaces from '../views/namespaces/ListNamespaces'
|
||||||
// Team Handling
|
|
||||||
import ListTeamsComponent from '../views/teams/ListTeams'
|
|
||||||
// Label Handling
|
// Label Handling
|
||||||
import ListLabelsComponent from '../views/labels/ListLabels'
|
import ListLabelsComponent from '../views/labels/ListLabels'
|
||||||
import NewLabelComponent from '../views/labels/NewLabel'
|
import NewLabelComponent from '../views/labels/NewLabel'
|
||||||
|
@ -65,8 +64,10 @@ const NewListComponent = () => import('../views/list/NewList')
|
||||||
// Namespace Handling
|
// Namespace Handling
|
||||||
const NewNamespaceComponent = () => import('../views/namespaces/NewNamespace')
|
const NewNamespaceComponent = () => import('../views/namespaces/NewNamespace')
|
||||||
|
|
||||||
const EditTeamComponent = () => import('../views/teams/EditTeam')
|
// Team Handling
|
||||||
const NewTeamComponent = () => import('../views/teams/NewTeam')
|
const Teams = () => import('@/views/teams/Teams')
|
||||||
|
const TeamsEdit = () => import('@/views/teams/TeamsEdit')
|
||||||
|
const TeamsNew = () => import('@/views/teams/TeamsNew')
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
|
@ -505,19 +506,19 @@ const router = createRouter({
|
||||||
{
|
{
|
||||||
path: '/teams',
|
path: '/teams',
|
||||||
name: 'teams.index',
|
name: 'teams.index',
|
||||||
component: ListTeamsComponent,
|
component: Teams,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/teams/new',
|
path: '/teams/new',
|
||||||
name: 'teams.create',
|
name: 'teams.create',
|
||||||
components: {
|
components: {
|
||||||
popup: NewTeamComponent,
|
popup: TeamsNew,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/teams/:id/edit',
|
path: '/teams/:id/edit',
|
||||||
name: 'teams.edit',
|
name: 'teams.edit',
|
||||||
component: EditTeamComponent,
|
component: TeamsEdit,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/labels',
|
path: '/labels',
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import AbstractService from './abstractService'
|
import AbstractService from './abstractService'
|
||||||
import TeamModel from '../models/team'
|
|
||||||
|
import TeamModel from '@/models/team'
|
||||||
|
|
||||||
import {formatISO} from 'date-fns'
|
import {formatISO} from 'date-fns'
|
||||||
|
|
||||||
export default class TeamService extends AbstractService {
|
export default class TeamService extends AbstractService {
|
||||||
|
|
|
@ -0,0 +1,225 @@
|
||||||
|
import { reactive, unref, watch, computed, watchEffect, shallowReactive } from 'vue'
|
||||||
|
import router from '@/router'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import TeamService from '@/services/team'
|
||||||
|
import TeamModel from '@/models/team'
|
||||||
|
import TeamMemberService from '@/services/teamMember'
|
||||||
|
import TeamMemberModel from '@/models/teamMember'
|
||||||
|
import Rights from '@/models/constants/rights.json'
|
||||||
|
|
||||||
|
import { success } from '@/message'
|
||||||
|
|
||||||
|
import { defineStore, storeToRefs, acceptHMRUpdate } from 'pinia'
|
||||||
|
|
||||||
|
// the first argument is a unique id of the store across your application
|
||||||
|
export const useTeamStore = defineStore('team', () => {
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const teamService = shallowReactive(new TeamService())
|
||||||
|
const teamServiceLoading = computed(() => teamService.loading)
|
||||||
|
|
||||||
|
const teamMemberService = shallowReactive(new TeamMemberService())
|
||||||
|
const teamMemberServiceLoading = computed(() => teamMemberService.loading)
|
||||||
|
|
||||||
|
|
||||||
|
const teams = reactive({})
|
||||||
|
const members = reactive({})
|
||||||
|
|
||||||
|
// create getters
|
||||||
|
function getTeamMembersByTeamId(teamId) {
|
||||||
|
return teams?.[teamId].memberIds.map((memberId) => members[memberId])
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTeam(unformattedTeam) {
|
||||||
|
const { members, ...team } = unformattedTeam
|
||||||
|
|
||||||
|
setMembers(members)
|
||||||
|
|
||||||
|
team.memberIds = members.map(({ id }) => id)
|
||||||
|
teams[team.id] = team
|
||||||
|
return team
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMembers(members) {
|
||||||
|
members.forEach((member) => {
|
||||||
|
members[member.id] = member
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadAllTeams() {
|
||||||
|
console.log('loadAllTeams')
|
||||||
|
const newTeams = await teamService.getAll()
|
||||||
|
newTeams.forEach((team) => setTeam(team))
|
||||||
|
|
||||||
|
console.log(newTeams)
|
||||||
|
console.log(teams)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadTeam(teamId) {
|
||||||
|
setTeam(new TeamModel({ id: teamId }))
|
||||||
|
const unformattedTeam = await teamService.get(teams[teamId])
|
||||||
|
return setTeam(unformattedTeam)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function newTeam(team) {
|
||||||
|
if (team.name === '') {
|
||||||
|
throw new Error(t('team.attributes.nameRequired'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const newTeam = await teamService.create(team)
|
||||||
|
setTeam(newTeam)
|
||||||
|
router.push({
|
||||||
|
name: 'teams.edit',
|
||||||
|
params: { id: newTeam.id },
|
||||||
|
})
|
||||||
|
|
||||||
|
success({ message: t('team.create.success') })
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateTeam(team) {
|
||||||
|
if (team.name === '') {
|
||||||
|
throw new Error(t('team.attributes.nameRequired'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const newTeam = new TeamMemberModel({
|
||||||
|
...team,
|
||||||
|
members: getTeamMembersByTeamId(team.id),
|
||||||
|
})
|
||||||
|
|
||||||
|
const unformattedTeam = await teamService.update(newTeam)
|
||||||
|
setTeam(unformattedTeam)
|
||||||
|
|
||||||
|
success({ message: t('team.edit.success') })
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteTeam(teamId) {
|
||||||
|
await teamService.delete(teams[teamId])
|
||||||
|
delete teams[teamId]
|
||||||
|
|
||||||
|
success({ message: t('team.edit.delete.success') })
|
||||||
|
|
||||||
|
router.push({ name: 'teams.index' })
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function deleteTeamMember(teamMemberId) {
|
||||||
|
const teamId = members[teamMemberId].teamId
|
||||||
|
await teamMemberService.delete(members[teamMemberId])
|
||||||
|
|
||||||
|
teams[teamId].members = teams[teamId].members.filter(
|
||||||
|
(id) => id !== teamMemberId,
|
||||||
|
)
|
||||||
|
delete members[teamMemberId]
|
||||||
|
|
||||||
|
success({ message: t('team.edit.deleteUser.success') })
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addTeamMember(user, teamId) {
|
||||||
|
const newMember = new TeamMemberModel({
|
||||||
|
teamId,
|
||||||
|
username: user.username,
|
||||||
|
})
|
||||||
|
const member = teamMemberService.create(newMember)
|
||||||
|
|
||||||
|
setMembers([member])
|
||||||
|
teams[teamId].members.push(member.id)
|
||||||
|
|
||||||
|
success({ message: t('team.edit.userAddedSuccess') })
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleMemberType(memberId) {
|
||||||
|
const member = members[memberId]
|
||||||
|
|
||||||
|
const newMember = {
|
||||||
|
admin: !member.admin,
|
||||||
|
teamId: member.teamId,
|
||||||
|
}
|
||||||
|
const updatedMember = await teamMemberService.update(newMember)
|
||||||
|
setMembers([updatedMember])
|
||||||
|
|
||||||
|
// FIXME: update userservice ?
|
||||||
|
|
||||||
|
success({
|
||||||
|
message: member.admin
|
||||||
|
? t('team.edit.madeAdmin')
|
||||||
|
: t('team.edit.madeMember'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watchEffect(() => loadAllTeams())
|
||||||
|
|
||||||
|
return {
|
||||||
|
// state
|
||||||
|
// TODO: add readonly()
|
||||||
|
teams,
|
||||||
|
// teams: readonly(teams),
|
||||||
|
members,
|
||||||
|
// members: readonly(members),
|
||||||
|
|
||||||
|
// getters
|
||||||
|
teamServiceLoading,
|
||||||
|
teamMemberServiceLoading,
|
||||||
|
|
||||||
|
// ACTIONS
|
||||||
|
// team
|
||||||
|
setMembers,
|
||||||
|
loadAllTeams,
|
||||||
|
loadTeam,
|
||||||
|
newTeam,
|
||||||
|
updateTeam,
|
||||||
|
deleteTeam,
|
||||||
|
|
||||||
|
// members
|
||||||
|
deleteTeamMember,
|
||||||
|
addTeamMember,
|
||||||
|
toggleMemberType,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useTeamStore, import.meta.hot))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useTeam(teamId) {
|
||||||
|
const teamStore = useTeamStore()
|
||||||
|
const {
|
||||||
|
members,
|
||||||
|
addTeamMember,
|
||||||
|
deleteTeamMember,
|
||||||
|
newTeam,
|
||||||
|
updateTeam,
|
||||||
|
deleteTeam,
|
||||||
|
} = teamStore
|
||||||
|
|
||||||
|
const team = reactive(new TeamModel())
|
||||||
|
|
||||||
|
const isNewTeam = computed(() => Boolean(unref(teamId)))
|
||||||
|
|
||||||
|
watch(() => unref(teamId), () => {
|
||||||
|
if (isNewTeam.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
teamStore.loadTeam(unref(teamId)).then((loadedTeam) => {
|
||||||
|
Object.assign(team, loadedTeam)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const userIsAdmin = computed(() => team?.maxRight > Rights.READ)
|
||||||
|
|
||||||
|
const {teamServiceLoading, teamMemberServiceLoading} = storeToRefs(teamStore)
|
||||||
|
|
||||||
|
return {
|
||||||
|
teamServiceLoading,
|
||||||
|
teamMemberServiceLoading,
|
||||||
|
team,
|
||||||
|
members,
|
||||||
|
addTeamMember: (user) => addTeamMember(user, teamId),
|
||||||
|
deleteTeamMember,
|
||||||
|
newTeam: () => newTeam(team),
|
||||||
|
updateTeam: () => updateTeam(team),
|
||||||
|
deleteTeam: () => deleteTeam(teamId),
|
||||||
|
userIsAdmin,
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,311 +0,0 @@
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
class="loader-container is-max-width-desktop"
|
|
||||||
:class="{ 'is-loading': teamService.loading }"
|
|
||||||
>
|
|
||||||
<card class="is-fullwidth" v-if="userIsAdmin" :title="title">
|
|
||||||
<form @submit.prevent="save()">
|
|
||||||
<div class="field">
|
|
||||||
<label class="label" for="teamtext">{{ $t('team.attributes.name') }}</label>
|
|
||||||
<div class="control">
|
|
||||||
<input
|
|
||||||
:class="{ disabled: teamMemberService.loading }"
|
|
||||||
:disabled="teamMemberService.loading || null"
|
|
||||||
class="input"
|
|
||||||
id="teamtext"
|
|
||||||
:placeholder="$t('team.attributes.namePlaceholder')"
|
|
||||||
type="text"
|
|
||||||
v-focus
|
|
||||||
v-model="team.name"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
class="help is-danger"
|
|
||||||
v-if="showError && team.name === ''"
|
|
||||||
>
|
|
||||||
{{ $t('team.attributes.nameRequired') }}
|
|
||||||
</p>
|
|
||||||
<div class="field">
|
|
||||||
<label class="label" for="teamdescription">{{ $t('team.attributes.description') }}</label>
|
|
||||||
<div class="control">
|
|
||||||
<editor
|
|
||||||
:class="{ disabled: teamService.loading }"
|
|
||||||
:disabled="teamService.loading"
|
|
||||||
:preview-is-default="false"
|
|
||||||
id="teamdescription"
|
|
||||||
:placeholder="$t('team.attributes.descriptionPlaceholder')"
|
|
||||||
v-model="team.description"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div class="field has-addons mt-4">
|
|
||||||
<div class="control is-fullwidth">
|
|
||||||
<x-button
|
|
||||||
@click="save()"
|
|
||||||
:loading="teamService.loading"
|
|
||||||
class="is-fullwidth"
|
|
||||||
>
|
|
||||||
{{ $t('misc.save') }}
|
|
||||||
</x-button>
|
|
||||||
</div>
|
|
||||||
<div class="control">
|
|
||||||
<x-button
|
|
||||||
@click="showDeleteModal = true"
|
|
||||||
:loading="teamService.loading"
|
|
||||||
class="is-danger"
|
|
||||||
icon="trash-alt"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</card>
|
|
||||||
|
|
||||||
<card class="is-fullwidth has-overflow" :title="$t('team.edit.members')" :padding="false">
|
|
||||||
<div class="p-4" v-if="userIsAdmin">
|
|
||||||
<div class="field has-addons">
|
|
||||||
<div class="control is-expanded">
|
|
||||||
<multiselect
|
|
||||||
:loading="userService.loading"
|
|
||||||
:placeholder="$t('team.edit.search')"
|
|
||||||
@search="findUser"
|
|
||||||
:search-results="foundUsers"
|
|
||||||
label="username"
|
|
||||||
v-model="newMember"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="control">
|
|
||||||
<x-button @click="addUser" icon="plus">
|
|
||||||
{{ $t('team.edit.addUser') }}
|
|
||||||
</x-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<table class="table has-actions is-striped is-hoverable is-fullwidth">
|
|
||||||
<tbody>
|
|
||||||
<tr :key="m.id" v-for="m in team.members">
|
|
||||||
<td>{{ m.getDisplayName() }}</td>
|
|
||||||
<td>
|
|
||||||
<template v-if="m.id === userInfo.id">
|
|
||||||
<b class="is-success">You</b>
|
|
||||||
</template>
|
|
||||||
</td>
|
|
||||||
<td class="type">
|
|
||||||
<template v-if="m.admin">
|
|
||||||
<span class="icon is-small">
|
|
||||||
<icon icon="lock"/>
|
|
||||||
</span>
|
|
||||||
{{ $t('team.attributes.admin') }}
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<span class="icon is-small">
|
|
||||||
<icon icon="user"/>
|
|
||||||
</span>
|
|
||||||
{{ $t('team.attributes.member') }}
|
|
||||||
</template>
|
|
||||||
</td>
|
|
||||||
<td class="actions" v-if="userIsAdmin">
|
|
||||||
<x-button
|
|
||||||
:loading="teamMemberService.loading"
|
|
||||||
@click="() => toggleUserType(m)"
|
|
||||||
class="mr-2"
|
|
||||||
v-if="m.id !== userInfo.id"
|
|
||||||
>
|
|
||||||
{{ m.admin ? $t('team.edit.makeMember') : $t('team.edit.makeAdmin') }}
|
|
||||||
</x-button>
|
|
||||||
<x-button
|
|
||||||
:loading="teamMemberService.loading"
|
|
||||||
@click="() => {member = m; showUserDeleteModal = true}"
|
|
||||||
class="is-danger"
|
|
||||||
v-if="m.id !== userInfo.id"
|
|
||||||
icon="trash-alt"
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</card>
|
|
||||||
|
|
||||||
<!-- Team delete modal -->
|
|
||||||
<transition name="modal">
|
|
||||||
<modal
|
|
||||||
@close="showDeleteModal = false"
|
|
||||||
@submit="deleteTeam()"
|
|
||||||
v-if="showDeleteModal"
|
|
||||||
>
|
|
||||||
<template #header><span>{{ $t('team.edit.delete.header') }}</span></template>
|
|
||||||
|
|
||||||
<template #text>
|
|
||||||
<p>{{ $t('team.edit.delete.text1') }}<br/>
|
|
||||||
{{ $t('team.edit.delete.text2') }}</p>
|
|
||||||
</template>
|
|
||||||
</modal>
|
|
||||||
</transition>
|
|
||||||
<!-- User delete modal -->
|
|
||||||
<transition name="modal">
|
|
||||||
<modal
|
|
||||||
@close="showUserDeleteModal = false"
|
|
||||||
@submit="deleteUser()"
|
|
||||||
v-if="showUserDeleteModal"
|
|
||||||
>
|
|
||||||
<template #header><span>{{ $t('team.edit.deleteUser.header') }}</span></template>
|
|
||||||
|
|
||||||
<template #text>
|
|
||||||
<p>{{ $t('team.edit.deleteUser.text1') }}<br/>
|
|
||||||
{{ $t('team.edit.deleteUser.text2') }}</p>
|
|
||||||
</template>
|
|
||||||
</modal>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import AsyncEditor from '@/components/input/AsyncEditor'
|
|
||||||
import {mapState} from 'vuex'
|
|
||||||
import { i18n } from '@/i18n'
|
|
||||||
|
|
||||||
import TeamService from '../../services/team'
|
|
||||||
import TeamModel from '../../models/team'
|
|
||||||
import TeamMemberService from '../../services/teamMember'
|
|
||||||
import TeamMemberModel from '../../models/teamMember'
|
|
||||||
import UserModel from '../../models/user'
|
|
||||||
import UserService from '../../services/user'
|
|
||||||
import Rights from '../../models/constants/rights.json'
|
|
||||||
|
|
||||||
import Multiselect from '@/components/input/multiselect.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'EditTeam',
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
teamService: new TeamService(),
|
|
||||||
teamMemberService: new TeamMemberService(),
|
|
||||||
team: TeamModel,
|
|
||||||
teamId: this.$route.params.id,
|
|
||||||
member: TeamMemberModel,
|
|
||||||
|
|
||||||
showDeleteModal: false,
|
|
||||||
showUserDeleteModal: false,
|
|
||||||
|
|
||||||
newMember: UserModel,
|
|
||||||
foundUsers: [],
|
|
||||||
userService: new UserService(),
|
|
||||||
|
|
||||||
showError: false,
|
|
||||||
title: '',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
Multiselect,
|
|
||||||
editor: AsyncEditor,
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
// call again the method if the route changes
|
|
||||||
'$route': {
|
|
||||||
handler: 'loadTeam',
|
|
||||||
deep: true,
|
|
||||||
immediate: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
userIsAdmin() {
|
|
||||||
return (
|
|
||||||
this.team &&
|
|
||||||
this.team.maxRight &&
|
|
||||||
this.team.maxRight > Rights.READ
|
|
||||||
)
|
|
||||||
},
|
|
||||||
...mapState({
|
|
||||||
userInfo: (state) => state.auth.info,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
async loadTeam() {
|
|
||||||
this.team = new TeamModel({id: this.teamId})
|
|
||||||
this.team = await this.teamService.get(this.team)
|
|
||||||
this.title = i18n.global.t('team.edit.title', {team: this.team.name})
|
|
||||||
this.setTitle(this.title)
|
|
||||||
},
|
|
||||||
|
|
||||||
async save() {
|
|
||||||
if (this.team.name === '') {
|
|
||||||
this.showError = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.showError = false
|
|
||||||
|
|
||||||
this.team = await this.teamService.update(this.team)
|
|
||||||
this.$message.success({message: this.$t('team.edit.success')})
|
|
||||||
},
|
|
||||||
|
|
||||||
async deleteTeam() {
|
|
||||||
await this.teamService.delete(this.team)
|
|
||||||
this.$message.success({message: this.$t('team.edit.delete.success')})
|
|
||||||
this.$router.push({name: 'teams.index'})
|
|
||||||
},
|
|
||||||
|
|
||||||
async deleteUser() {
|
|
||||||
try {
|
|
||||||
await this.teamMemberService.delete(this.member)
|
|
||||||
this.$message.success({message: this.$t('team.edit.deleteUser.success')})
|
|
||||||
this.loadTeam()
|
|
||||||
} finally {
|
|
||||||
this.showUserDeleteModal = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async addUser() {
|
|
||||||
const newMember = new TeamMemberModel({
|
|
||||||
teamId: this.teamId,
|
|
||||||
username: this.newMember.username,
|
|
||||||
})
|
|
||||||
await this.teamMemberService.create(newMember)
|
|
||||||
this.loadTeam()
|
|
||||||
this.$message.success({message: this.$t('team.edit.userAddedSuccess')})
|
|
||||||
},
|
|
||||||
|
|
||||||
async toggleUserType(member) {
|
|
||||||
// FIXME: direct manipulation
|
|
||||||
member.admin = !member.admin
|
|
||||||
member.teamId = this.teamId
|
|
||||||
const r = await this.teamMemberService.update(member)
|
|
||||||
for (const tm in this.team.members) {
|
|
||||||
if (this.team.members[tm].id === member.id) {
|
|
||||||
this.team.members[tm].admin = r.admin
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.$message.success({
|
|
||||||
message: member.admin ?
|
|
||||||
this.$t('team.edit.madeAdmin') :
|
|
||||||
this.$t('team.edit.madeMember'),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
async findUser(query) {
|
|
||||||
if (query === '') {
|
|
||||||
this.clearAll()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.foundUsers = await this.userService.getAll({}, {s: query})
|
|
||||||
},
|
|
||||||
|
|
||||||
clearAll() {
|
|
||||||
this.foundUsers = []
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.card.is-fullwidth {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
|
|
||||||
.content {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,80 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="content loader-container is-max-width-desktop" :class="{ 'is-loading': teamService.loading}">
|
|
||||||
<x-button
|
|
||||||
:to="{name:'teams.create'}"
|
|
||||||
class="is-pulled-right"
|
|
||||||
icon="plus"
|
|
||||||
>
|
|
||||||
{{ $t('team.create.title') }}
|
|
||||||
</x-button>
|
|
||||||
|
|
||||||
<h1>{{ $t('team.title') }}</h1>
|
|
||||||
<ul class="teams box" v-if="teams.length > 0">
|
|
||||||
<li :key="t.id" v-for="t in teams">
|
|
||||||
<router-link :to="{name: 'teams.edit', params: {id: t.id}}">
|
|
||||||
{{ t.name }}
|
|
||||||
</router-link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<p v-else-if="!teamService.loading" class="has-text-centered has-text-grey is-italic">
|
|
||||||
{{ $t('team.noTeams') }}
|
|
||||||
<router-link :to="{name: 'teams.create'}">
|
|
||||||
{{ $t('team.create.title') }}.
|
|
||||||
</router-link>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import TeamService from '../../services/team'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'ListTeams',
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
teamService: new TeamService(),
|
|
||||||
teams: [],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.loadTeams()
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.setTitle(this.$t('team.title'))
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async loadTeams() {
|
|
||||||
this.teams = await this.teamService.getAll()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
ul.teams {
|
|
||||||
padding: 0;
|
|
||||||
margin-left: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
li {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
border-bottom: 1px solid $border;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #363636;
|
|
||||||
display: block;
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
transition: background-color $transition;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--grey-100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
li:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,68 +0,0 @@
|
||||||
<template>
|
|
||||||
<create-edit
|
|
||||||
:title="$t('team.create.title')"
|
|
||||||
@create="newTeam()"
|
|
||||||
:primary-disabled="team.name === ''"
|
|
||||||
>
|
|
||||||
<div class="field">
|
|
||||||
<label class="label" for="teamName">{{ $t('team.attributes.name') }}</label>
|
|
||||||
<div
|
|
||||||
class="control is-expanded"
|
|
||||||
:class="{ 'is-loading': teamService.loading }"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
:class="{ 'disabled': teamService.loading }"
|
|
||||||
class="input"
|
|
||||||
id="teamName"
|
|
||||||
:placeholder="$t('team.attributes.namePlaceholder')"
|
|
||||||
type="text"
|
|
||||||
v-focus
|
|
||||||
v-model="team.name"
|
|
||||||
@keyup.enter="newTeam"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p class="help is-danger" v-if="showError && team.name === ''">
|
|
||||||
{{ $t('team.attributes.nameRequired') }}
|
|
||||||
</p>
|
|
||||||
</create-edit>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import TeamModel from '../../models/team'
|
|
||||||
import TeamService from '../../services/team'
|
|
||||||
import CreateEdit from '@/components/misc/create-edit.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'NewTeam',
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
teamService: new TeamService(),
|
|
||||||
team: new TeamModel(),
|
|
||||||
showError: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
CreateEdit,
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.setTitle(this.$t('team.create.title'))
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async newTeam() {
|
|
||||||
if (this.team.name === '') {
|
|
||||||
this.showError = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.showError = false
|
|
||||||
|
|
||||||
const response = await this.teamService.create(this.team)
|
|
||||||
this.$router.push({
|
|
||||||
name: 'teams.edit',
|
|
||||||
params: { id: response.id },
|
|
||||||
})
|
|
||||||
this.$message.success({message: this.$t('team.create.success') })
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
<template>
|
||||||
|
<div class="content loader-container is-max-width-desktop" :class="{ 'is-loading': teamServiceLoading}">
|
||||||
|
<x-button
|
||||||
|
:to="{name:'teams.create'}"
|
||||||
|
class="is-pulled-right"
|
||||||
|
icon="plus"
|
||||||
|
>
|
||||||
|
{{ $t('team.create.title') }}
|
||||||
|
</x-button>
|
||||||
|
|
||||||
|
<h1>{{ $t('team.title') }}</h1>
|
||||||
|
<ul class="team-list box" v-if="Object.keys(teams).length > 0">
|
||||||
|
<li class="team-item" :key="t.id" v-for="t in teams">
|
||||||
|
<router-link class="team-link" :to="{name: 'teams.edit', params: {id: t.id}}">
|
||||||
|
{{ t.name }}
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p v-else-if="!teamServiceLoading" class="has-text-centered has-text-grey is-italic">
|
||||||
|
{{ $t('team.noTeams') }}
|
||||||
|
<router-link :to="{name: 'teams.create'}">
|
||||||
|
{{ $t('team.create.title') }}.
|
||||||
|
</router-link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { watchEffect } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
|
||||||
|
import { useTitle } from '@/composables/useTitle'
|
||||||
|
import { useTeamStore } from '@/stores/teams'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
useTitle(() => t('team.title'))
|
||||||
|
|
||||||
|
function useTeams() {
|
||||||
|
const teamStore = useTeamStore()
|
||||||
|
|
||||||
|
watchEffect(() => teamStore.loadAllTeams())
|
||||||
|
|
||||||
|
const {teamServiceLoading} = storeToRefs(teamStore)
|
||||||
|
|
||||||
|
return {
|
||||||
|
teams: teamStore.teams,
|
||||||
|
teamServiceLoading,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const {teams, teamServiceLoading} = useTeams()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.team-list {
|
||||||
|
padding: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-item {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
> & + & {
|
||||||
|
border-top: 1px solid $border;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-link {
|
||||||
|
color: #363636;
|
||||||
|
display: block;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
transition: background-color $transition;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--grey-100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,259 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="loader-container is-max-width-desktop"
|
||||||
|
:class="{ 'is-loading': teamServiceLoading }"
|
||||||
|
>
|
||||||
|
<card class="is-fullwidth" v-if="userIsAdmin" :title="title">
|
||||||
|
<form @submit.prevent="saveTeam()">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="teamtext">{{ $t('team.attributes.name') }}</label>
|
||||||
|
<div class="control">
|
||||||
|
<input
|
||||||
|
:class="{ disabled: teamMemberServiceLoading }"
|
||||||
|
:disabled="teamMemberServiceLoading || null"
|
||||||
|
class="input"
|
||||||
|
id="teamtext"
|
||||||
|
:placeholder="$t('team.attributes.namePlaceholder')"
|
||||||
|
type="text"
|
||||||
|
v-focus
|
||||||
|
v-model="team.name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="help is-danger"
|
||||||
|
v-if="showError && team.name === ''"
|
||||||
|
>
|
||||||
|
{{ $t('team.attributes.nameRequired') }}
|
||||||
|
</p>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="teamdescription">{{ $t('team.attributes.description') }}</label>
|
||||||
|
<div class="control">
|
||||||
|
<editor
|
||||||
|
:class="{ disabled: teamServiceLoading }"
|
||||||
|
:disabled="teamServiceLoading"
|
||||||
|
:preview-is-default="false"
|
||||||
|
id="teamdescription"
|
||||||
|
:placeholder="$t('team.attributes.descriptionPlaceholder')"
|
||||||
|
v-model="team.description"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="field has-addons mt-4">
|
||||||
|
<div class="control is-fullwidth">
|
||||||
|
<x-button
|
||||||
|
@click="saveTeam()"
|
||||||
|
:loading="teamServiceLoading"
|
||||||
|
class="is-fullwidth"
|
||||||
|
>
|
||||||
|
{{ $t('misc.save') }}
|
||||||
|
</x-button>
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<x-button
|
||||||
|
@click="openTeamDeleteDialog()"
|
||||||
|
:loading="teamServiceLoading"
|
||||||
|
class="is-danger"
|
||||||
|
icon="trash-alt"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</card>
|
||||||
|
|
||||||
|
<card class="is-fullwidth has-overflow" :title="$t('team.edit.members')" :padding="false">
|
||||||
|
<div class="p-4" v-if="userIsAdmin">
|
||||||
|
<div class="field has-addons">
|
||||||
|
<div class="control is-expanded">
|
||||||
|
<multiselect
|
||||||
|
:loading="userService.loading"
|
||||||
|
:placeholder="$t('team.edit.search')"
|
||||||
|
@search="findUser"
|
||||||
|
:search-results="foundUsers"
|
||||||
|
label="username"
|
||||||
|
v-model="newMember"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="control">
|
||||||
|
<x-button @click="addTeamMember()" icon="plus">
|
||||||
|
{{ $t('team.edit.addUser') }}
|
||||||
|
</x-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table has-actions is-striped is-hoverable is-fullwidth">
|
||||||
|
<tbody>
|
||||||
|
<tr :key="m.id" v-for="m in members">
|
||||||
|
<td>{{ m.getDisplayName() }}</td>
|
||||||
|
<td>
|
||||||
|
<template v-if="m.id === userInfo.id">
|
||||||
|
<b class="is-success">You</b>
|
||||||
|
</template>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="type">
|
||||||
|
<template v-if="m.admin">
|
||||||
|
<span class="icon is-small">
|
||||||
|
<icon icon="lock"/>
|
||||||
|
</span>
|
||||||
|
{{ $t('team.attributes.admin') }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span class="icon is-small">
|
||||||
|
<icon icon="user"/>
|
||||||
|
</span>
|
||||||
|
{{ $t('team.attributes.member') }}
|
||||||
|
</template>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="actions" v-if="userIsAdmin && m.id !== userInfo.id">
|
||||||
|
<x-button
|
||||||
|
:loading="teamMemberServiceLoading"
|
||||||
|
@click="() => toggleMemberType(m)"
|
||||||
|
class="mr-2"
|
||||||
|
>
|
||||||
|
{{ m.admin ? $t('team.edit.makeMember') : $t('team.edit.makeAdmin') }}
|
||||||
|
</x-button>
|
||||||
|
<x-button
|
||||||
|
:loading="teamMemberServiceLoading"
|
||||||
|
@click="openTeamMemberDeleteDialog(m)"
|
||||||
|
class="is-danger"
|
||||||
|
icon="trash-alt"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</card>
|
||||||
|
|
||||||
|
<!-- Team delete modal -->
|
||||||
|
<transition name="modal">
|
||||||
|
<modal
|
||||||
|
@close="showTeamDeleteDialog = false"
|
||||||
|
@submit="deleteTeam()"
|
||||||
|
v-if="showTeamDeleteDialog"
|
||||||
|
>
|
||||||
|
<template #header><span>{{ $t('team.edit.delete.header') }}</span></template>
|
||||||
|
|
||||||
|
<template #text>
|
||||||
|
<p>{{ $t('team.edit.delete.text1') }}<br/>
|
||||||
|
{{ $t('team.edit.delete.text2') }}</p>
|
||||||
|
</template>
|
||||||
|
</modal>
|
||||||
|
</transition>
|
||||||
|
|
||||||
|
<!-- User delete modal -->
|
||||||
|
<transition name="modal">
|
||||||
|
<modal
|
||||||
|
v-if="showUserDeleteDialog"
|
||||||
|
@close="showUserDeleteDialog = false"
|
||||||
|
@submit="deleteTeamMember()"
|
||||||
|
>
|
||||||
|
<template #header><span>{{ $t('team.edit.deleteUser.header') }}</span></template>
|
||||||
|
|
||||||
|
<template #text>
|
||||||
|
<p>{{ $t('team.edit.deleteUser.text1') }}<br/>
|
||||||
|
{{ $t('team.edit.deleteUser.text2') }}</p>
|
||||||
|
</template>
|
||||||
|
</modal>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { store } from '@/store'
|
||||||
|
|
||||||
|
import {default as Editor} from '@/components/input/AsyncEditor'
|
||||||
|
import Multiselect from '@/components/input/multiselect.vue'
|
||||||
|
|
||||||
|
import UserModel from '@/models/user'
|
||||||
|
import UserService from '@/services/user'
|
||||||
|
import { useTeam } from '@/stores/teams'
|
||||||
|
import { useTitle } from '@/composables/useTitle'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const teamId = computed(() => route.params.id)
|
||||||
|
|
||||||
|
const {
|
||||||
|
teamServiceLoading,
|
||||||
|
teamMemberServiceLoading,
|
||||||
|
team,
|
||||||
|
members,
|
||||||
|
addTeamMember,
|
||||||
|
deleteTeamMember: deleteTeamMemberAction,
|
||||||
|
updateTeam,
|
||||||
|
deleteTeam,
|
||||||
|
userIsAdmin,
|
||||||
|
} = useTeam(teamId)
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const title = useTitle(() => t('team.edit.title', {team: team.name}))
|
||||||
|
|
||||||
|
const newMember = ref(new UserModel())
|
||||||
|
|
||||||
|
const showError = ref(false)
|
||||||
|
async function saveTeam() {
|
||||||
|
showError.value = false
|
||||||
|
try {
|
||||||
|
await updateTeam()
|
||||||
|
} catch (e) {
|
||||||
|
if (e.message === t('team.attributes.nameRequired')) {
|
||||||
|
showError.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const userInfo = computed(() => store.state.auth.info)
|
||||||
|
|
||||||
|
const userService = new UserService()
|
||||||
|
const foundUsers = ref([])
|
||||||
|
async function findUser(query) {
|
||||||
|
if (query === '') {
|
||||||
|
foundUsers.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
foundUsers.value = await userService.getAll({}, {s: query})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const showTeamDeleteDialog = ref(false)
|
||||||
|
|
||||||
|
function openTeamDeleteDialog() {
|
||||||
|
// FIXME: the delete dialog should be opened by a method and not via state change
|
||||||
|
showTeamDeleteDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const memberIdToDelete = ref(null)
|
||||||
|
const showUserDeleteDialog = ref(false)
|
||||||
|
|
||||||
|
function openTeamMemberDeleteDialog(memberId) {
|
||||||
|
memberIdToDelete.value = memberId
|
||||||
|
|
||||||
|
// FIXME: the delete dialog should be opened by a method and not via state change
|
||||||
|
showUserDeleteDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteTeamMember() {
|
||||||
|
try {
|
||||||
|
await deleteTeamMemberAction(memberIdToDelete.value)
|
||||||
|
} finally {
|
||||||
|
showUserDeleteDialog.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.card.is-fullwidth {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,58 @@
|
||||||
|
<template>
|
||||||
|
<create-edit
|
||||||
|
:title="$t('team.create.title')"
|
||||||
|
@create="newTeam()"
|
||||||
|
:primary-disabled="team.name === ''"
|
||||||
|
>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="teamName">{{ $t('team.attributes.name') }}</label>
|
||||||
|
<div
|
||||||
|
class="control is-expanded"
|
||||||
|
:class="{ 'is-loading': teamServiceLoading }"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
:class="{ 'disabled': teamServiceLoading }"
|
||||||
|
class="input"
|
||||||
|
id="teamName"
|
||||||
|
:placeholder="$t('team.attributes.namePlaceholder')"
|
||||||
|
type="text"
|
||||||
|
v-focus
|
||||||
|
v-model="team.name"
|
||||||
|
@keyup.enter="newTeam"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="help is-danger" v-if="showError && team.name === ''">
|
||||||
|
{{ $t('team.attributes.nameRequired') }}
|
||||||
|
</p>
|
||||||
|
</create-edit>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import CreateEdit from '@/components/misc/create-edit.vue'
|
||||||
|
|
||||||
|
import { useTeam } from '@/stores/teams'
|
||||||
|
import { useTitle } from '@/composables/useTitle'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
useTitle(() => t('team.create.title'))
|
||||||
|
|
||||||
|
const showError = ref(false)
|
||||||
|
|
||||||
|
const {teamServiceLoading, team, newTeam: newTeamAction} = useTeam()
|
||||||
|
|
||||||
|
async function newTeam() {
|
||||||
|
try {
|
||||||
|
await newTeamAction()
|
||||||
|
} catch(e) {
|
||||||
|
if (e.message === t('team.attributes.nameRequired')) {
|
||||||
|
showError.value = true
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
13
yarn.lock
13
yarn.lock
|
@ -3682,6 +3682,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.0.0-beta.19.tgz#f8e88059daa424515992426a0c7ea5cde07e99bf"
|
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.0.0-beta.19.tgz#f8e88059daa424515992426a0c7ea5cde07e99bf"
|
||||||
integrity sha512-ObzQhgkoVeoyKv+e8+tB/jQBL2smtk/NmC9OmFK8UqdDpoOdv/Kf9pyDWL+IFyM7qLD2C75rszJujvGSPSpGlw==
|
integrity sha512-ObzQhgkoVeoyKv+e8+tB/jQBL2smtk/NmC9OmFK8UqdDpoOdv/Kf9pyDWL+IFyM7qLD2C75rszJujvGSPSpGlw==
|
||||||
|
|
||||||
|
"@vue/devtools-api@^6.0.0-beta.20.1":
|
||||||
|
version "6.0.0-beta.20.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.0.0-beta.20.1.tgz#5b499647e929c35baf2a66a399578f9aa4601142"
|
||||||
|
integrity sha512-R2rfiRY+kZugzWh9ZyITaovx+jpU4vgivAEAiz80kvh3yviiTU3CBuGuyWpSwGz9/C7TkSWVM/FtQRGlZ16n8Q==
|
||||||
|
|
||||||
"@vue/eslint-config-typescript@9.1.0":
|
"@vue/eslint-config-typescript@9.1.0":
|
||||||
version "9.1.0"
|
version "9.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/eslint-config-typescript/-/eslint-config-typescript-9.1.0.tgz#b98a64352b312085444a08b98728962e2a8425ab"
|
resolved "https://registry.yarnpkg.com/@vue/eslint-config-typescript/-/eslint-config-typescript-9.1.0.tgz#b98a64352b312085444a08b98728962e2a8425ab"
|
||||||
|
@ -11231,6 +11236,14 @@ pify@^4.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
|
resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
|
||||||
integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==
|
integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==
|
||||||
|
|
||||||
|
pinia@^2.0.4:
|
||||||
|
version "2.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/pinia/-/pinia-2.0.4.tgz#06f6a03f6f19e6ec8b63cc06459011d96948e53d"
|
||||||
|
integrity sha512-nAc2f9HmOcBbWRlnGDuBGedM1G6uFAR10FnJWP1/dgm1I2tM5jbgKL/3IgynP4mBnPCy//ky7g0WpCZl5Mmxsg==
|
||||||
|
dependencies:
|
||||||
|
"@vue/devtools-api" "^6.0.0-beta.20.1"
|
||||||
|
vue-demi "*"
|
||||||
|
|
||||||
pinkie-promise@^2.0.0:
|
pinkie-promise@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
|
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
|
||||||
|
|
Reference in New Issue