From 04aa74a01fb52d0707fca6ee4cc04018f99d34af Mon Sep 17 00:00:00 2001 From: Dominik Pschenitschni Date: Sat, 27 Nov 2021 14:28:17 +0100 Subject: [PATCH] feat: use script setup for team views WIP --- package.json | 1 + src/main.ts | 7 +- src/router/index.js | 15 +- src/services/team.js | 4 +- src/stores/teams.js | 225 ++++++++++++++++++++++++ src/views/teams/EditTeam.vue | 311 ---------------------------------- src/views/teams/ListTeams.vue | 80 --------- src/views/teams/NewTeam.vue | 68 -------- src/views/teams/Teams.vue | 82 +++++++++ src/views/teams/TeamsEdit.vue | 259 ++++++++++++++++++++++++++++ src/views/teams/TeamsNew.vue | 58 +++++++ yarn.lock | 13 ++ 12 files changed, 654 insertions(+), 469 deletions(-) create mode 100644 src/stores/teams.js delete mode 100644 src/views/teams/EditTeam.vue delete mode 100644 src/views/teams/ListTeams.vue delete mode 100644 src/views/teams/NewTeam.vue create mode 100644 src/views/teams/Teams.vue create mode 100644 src/views/teams/TeamsEdit.vue create mode 100644 src/views/teams/TeamsNew.vue diff --git a/package.json b/package.json index 97949abef..f0f6b3781 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "lodash.clonedeep": "4.5.0", "lodash.debounce": "4.0.8", "marked": "4.0.5", + "pinia": "^2.0.4", "register-service-worker": "1.7.2", "snake-case": "3.0.4", "ufo": "0.7.9", diff --git a/src/main.ts b/src/main.ts index 48a6590d3..622a8917e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -33,6 +33,8 @@ import './registerServiceWorker' // Vuex import {store} from './store' +// Pinia +import { createPinia } from 'pinia' // i18n import {i18n} from './i18n' @@ -133,8 +135,9 @@ if (window.SENTRY_ENABLED) { import('./sentry').then(sentry => sentry.default(app, router)) } -app.use(router) -app.use(store) app.use(i18n) +app.use(store) +app.use(createPinia()) +app.use(router) app.mount('#app') \ No newline at end of file diff --git a/src/router/index.js b/src/router/index.js index 1f876f880..37d170f22 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -14,8 +14,7 @@ import LinkShareAuthComponent from '../views/sharing/LinkSharingAuth' import TaskDetailViewModal from '../views/tasks/TaskDetailViewModal' import TaskDetailView from '../views/tasks/TaskDetailView' import ListNamespaces from '../views/namespaces/ListNamespaces' -// Team Handling -import ListTeamsComponent from '../views/teams/ListTeams' + // Label Handling import ListLabelsComponent from '../views/labels/ListLabels' import NewLabelComponent from '../views/labels/NewLabel' @@ -65,8 +64,10 @@ const NewListComponent = () => import('../views/list/NewList') // Namespace Handling const NewNamespaceComponent = () => import('../views/namespaces/NewNamespace') -const EditTeamComponent = () => import('../views/teams/EditTeam') -const NewTeamComponent = () => import('../views/teams/NewTeam') +// Team Handling +const Teams = () => import('@/views/teams/Teams') +const TeamsEdit = () => import('@/views/teams/TeamsEdit') +const TeamsNew = () => import('@/views/teams/TeamsNew') const router = createRouter({ history: createWebHistory(), @@ -505,19 +506,19 @@ const router = createRouter({ { path: '/teams', name: 'teams.index', - component: ListTeamsComponent, + component: Teams, }, { path: '/teams/new', name: 'teams.create', components: { - popup: NewTeamComponent, + popup: TeamsNew, }, }, { path: '/teams/:id/edit', name: 'teams.edit', - component: EditTeamComponent, + component: TeamsEdit, }, { path: '/labels', diff --git a/src/services/team.js b/src/services/team.js index e14fd9314..4e989dde9 100644 --- a/src/services/team.js +++ b/src/services/team.js @@ -1,5 +1,7 @@ import AbstractService from './abstractService' -import TeamModel from '../models/team' + +import TeamModel from '@/models/team' + import {formatISO} from 'date-fns' export default class TeamService extends AbstractService { diff --git a/src/stores/teams.js b/src/stores/teams.js new file mode 100644 index 000000000..0983e6953 --- /dev/null +++ b/src/stores/teams.js @@ -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, + } +} \ No newline at end of file diff --git a/src/views/teams/EditTeam.vue b/src/views/teams/EditTeam.vue deleted file mode 100644 index 059530e8e..000000000 --- a/src/views/teams/EditTeam.vue +++ /dev/null @@ -1,311 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/views/teams/ListTeams.vue b/src/views/teams/ListTeams.vue deleted file mode 100644 index 503a8b3d1..000000000 --- a/src/views/teams/ListTeams.vue +++ /dev/null @@ -1,80 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/views/teams/NewTeam.vue b/src/views/teams/NewTeam.vue deleted file mode 100644 index c5a1afb01..000000000 --- a/src/views/teams/NewTeam.vue +++ /dev/null @@ -1,68 +0,0 @@ - - - diff --git a/src/views/teams/Teams.vue b/src/views/teams/Teams.vue new file mode 100644 index 000000000..9286f83c9 --- /dev/null +++ b/src/views/teams/Teams.vue @@ -0,0 +1,82 @@ + + + + + \ No newline at end of file diff --git a/src/views/teams/TeamsEdit.vue b/src/views/teams/TeamsEdit.vue new file mode 100644 index 000000000..f184904c7 --- /dev/null +++ b/src/views/teams/TeamsEdit.vue @@ -0,0 +1,259 @@ + + + + + \ No newline at end of file diff --git a/src/views/teams/TeamsNew.vue b/src/views/teams/TeamsNew.vue new file mode 100644 index 000000000..7db423b78 --- /dev/null +++ b/src/views/teams/TeamsNew.vue @@ -0,0 +1,58 @@ + + + diff --git a/yarn.lock b/yarn.lock index 01057caf3..8b3f61cd5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3682,6 +3682,11 @@ 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== +"@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": version "9.1.0" 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" 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: version "2.0.1" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" -- 2.40.1