Browse Source

chore: first commit

master
Shalma 1 year ago
commit
b93ee63584
  1. 1
      .env.production
  2. 30
      .gitignore
  3. 23
      Dockerfile
  4. 29
      README.md
  5. 10
      docker-entrypoint.sh
  6. 15
      index.html
  7. 8
      jsconfig.json
  8. 19
      nginx.conf
  9. 1019
      package-lock.json
  10. 22
      package.json
  11. BIN
      public/favicon.ico
  12. 117
      src/App.vue
  13. 67
      src/assets/main.css
  14. 142
      src/components/BisList.vue
  15. 177
      src/components/HistoryList.vue
  16. 236
      src/components/RosterList.vue
  17. 28
      src/main.js
  18. 29
      src/plugins/httpRequest.js
  19. 19
      vite.config.js

1
.env.production

@ -0,0 +1 @@
VITE_SERVER_SUIVI_URL=VITE_SERVER_URL_PLACEHOLDER

30
.gitignore vendored

@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

23
Dockerfile

@ -0,0 +1,23 @@
FROM node:20-alpine as build-stage
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
# #########################
FROM nginx as production-stage
# Copy dependencies from builder
COPY --from=build-stage /app/dist /usr/share/nginx/html
COPY docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 3020
ENTRYPOINT "/docker-entrypoint.sh"

29
README.md

@ -0,0 +1,29 @@
# suivi-loot-wow
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Compile and Minify for Production
```sh
npm run build
```

10
docker-entrypoint.sh

@ -0,0 +1,10 @@
# !/bin/sh
ROOT_DIR=/usr/share/nginx/html
for file in $ROOT_DIR/*.js $ROOT_DIR/index.html $ROOT_DIR/assets/*.js*;
do
sed -i 's|VITE_SERVER_URL_PLACEHOLDER|'${VITE_SERVER_SUIVI_URL}'|g' $file
done
nginx -g 'daemon off;'

15
index.html

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Suivi Loots</title>
</head>
<body>
<div id="app"></div>
<script>const whTooltips = { colorLinks: true, iconizeLinks: true, renameLinks: true };</script>
<script src="https://wow.zamimg.com/js/tooltips.js"></script>
<script type="module" src="/src/main.js"></script>
</body>
</html>

8
jsconfig.json

@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

19
nginx.conf

@ -0,0 +1,19 @@
gzip on;
gzip_disable "msie6";
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml font/ttf font/woff2 font/woff font/opentype image/png;
server {
listen 3020;
listen [::]:3020;
root /usr/share/nginx/html;
index index.html;
resolver 127.0.0.1;
location / {
try_files $uri $uri/ /index.html;
}
}

1019
package-lock.json generated

File diff suppressed because it is too large Load Diff

22
package.json

@ -0,0 +1,22 @@
{
"name": "suivi-loot-wow",
"version": "0.0.0",
"private": true,
"type": "module",
"author": "Shalma <sebcas@yahoo.fr>",
"scripts": {
"dev": "vite --host",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"luxon": "^3.4.4",
"vue": "^3.4.29",
"vuetify": "^3.6.13"
},
"devDependencies": {
"@mdi/font": "^7.4.47",
"@vitejs/plugin-vue": "^5.0.5",
"vite": "^5.3.1"
}
}

BIN
public/favicon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

117
src/App.vue

@ -0,0 +1,117 @@
<script setup>
import { ref, onMounted, onBeforeUnmount, computed } from 'vue'
import BisList from '@/components/BisList.vue'
import HistoryList from '@/components/HistoryList.vue'
import RosterList from '@/components/RosterList.vue'
import { httpRequest } from '@/plugins/httpRequest.js'
const tab = ref('bis-list')
const loginDialog = ref(false)
const username = ref('')
const password = ref('')
const errorMessage = ref('')
onMounted(() => {
document.addEventListener('keypress', eventKey)
})
onBeforeUnmount(() => {
document.removeEventListener('keypress', eventKey)
})
const eventKey = async (e) => {
if (e.keyCode === 13) {
e.preventDefault();
await submitLogin()
}
}
const isLogged = computed(() => {
if (localStorage.getItem('token')) return true
return false
})
const login = () => {
loginDialog.value = true
}
const cancelLogin = () => {
loginDialog.value = false
username.value = ''
password.value = ''
errorMessage.value = ''
}
const submitLogin = async () => {
const body = {
username: username.value,
password: password.value
}
try {
const result = await httpRequest('/signin', { method: 'POST', body }, false, true)
if (result?.success) {
localStorage.setItem('token', result.token)
cancelLogin()
location.reload()
} else errorMessage.value = 'Bar user or password'
} catch (e) {
errorMessage.value = e.message
}
}
</script>
<template>
<v-app>
<v-main>
<v-icon v-if="!isLogged" class="icon-login" icon="mdi-login" @click="login" />
<v-tabs v-model="tab" align-tabs="center" color="primary">
<v-tab value="bis-list">Bis List</v-tab>
<v-tab value="history">Historique</v-tab>
<v-tab value="roster">Roster</v-tab>
</v-tabs>
<v-tabs-window v-model="tab">
<v-tabs-window-item value="bis-list">
<bis-list v-if="tab === 'bis-list'" />
</v-tabs-window-item>
<v-tabs-window-item value="history">
<history-list v-if="tab === 'history'" />
</v-tabs-window-item>
<v-tabs-window-item value="roster">
<roster-list v-if="tab === 'roster'" />
</v-tabs-window-item>
</v-tabs-window>
<v-dialog max-width="400" persistent v-model="loginDialog">
<v-card title="Connection">
<v-card-text>
<v-form>
<v-container>
<v-row>
<v-col cols="12">
<v-text-field v-model="username" label="Identifiant" density="compact" hide-details required />
</v-col>
</v-row>
<v-row>
<v-col cols="12">
<v-text-field v-model="password" type="password" label="Mot de passe" density="compact" hide-details required />
</v-col>
</v-row>
<v-row>
<v-col cols="12">
<span class="text-error">{{ errorMessage }}</span>
</v-col>
</v-row>
</v-container>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn prepend-icon="mdi-cancel" text="Annuler" variant="outlined" color="warning" @click="cancelLogin" />
<v-btn prepend-icon="mdi-login" text="Se connecter" variant="outlined" color="primary" @click="submitLogin" />
</v-card-actions>
</v-card>
</v-dialog>
</v-main>
</v-app>
</template>

67
src/assets/main.css

@ -0,0 +1,67 @@
html {
overflow: hidden;
}
.color-DEATHKNIGHT {
color: #C41F3B;
}
.color-DRUID {
color: #FF7D0A;
}
.color-SHAMAN {
color: #0070DE;
}
.color-MAGE {
color: #40C7EB;
}
.color-HUNTER {
color: #A9D271;
}
.color-PRIEST {
color: #FFFFFF;
}
.color-WARRIOR {
color: #C79C6E;
}
.color-PALADIN {
color: #F58CBA;
}
.color-WARLOCK {
color: #8787ED;
}
.color-ROGUE {
color: #FFF569;
}
.pointer {
cursor: pointer;
}
.datatable-height {
max-height: 95dvh;
}
a, a:active, a:focus, a:hover {
color: #9E9EFF;
}
.select-row {
background-color: #444;
}
.icon-login {
position: absolute;
right: 30px;
top: 20px;
cursor: pointer;
z-index: 1;
}

142
src/components/BisList.vue

@ -0,0 +1,142 @@
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { httpRequest } from '@/plugins/httpRequest.js'
const itemsPerPage = ref(40)
const bisList = reactive([])
const selected = ref([])
const headers = [
{ value: 'name', title: 'Membre', sortable: true },
{ key: 'tete', value: 'data.tete', title: 'Tête', align: 'center' },
{ key: 'cou', value: 'data.cou', title: 'Cou', align: 'center' },
{ key: 'epaules', value: 'data.epaules', title: 'Épaules', align: 'center' },
{ key: 'dos', value: 'data.dos', title: 'Dos', align: 'center' },
{ key: 'torse', value: 'data.torse', title: 'Torse', align: 'center' },
{ key: 'poignets', value: 'data.poignets', title: 'Poignets', align: 'center' },
{ key: 'mains', value: 'data.mains', title: 'Mains', align: 'center' },
{ key: 'taille', value: 'data.taille', title: 'Taille', align: 'center' },
{ key: 'jambes', value: 'data.jambes', title: 'Jambes', align: 'center' },
{ key: 'pied', value: 'data.pied', title: 'Pieds', align: 'center' },
{ key: 'doigt1', value: 'data.doigt1', title: 'Doigt 1', align: 'center' },
{ key: 'doigt2', value: 'data.doigt2', title: 'Doigt 2', align: 'center' },
{ key: 'bijou1', value: 'data.bijou1', title: 'Bijou 1', align: 'center' },
{ key: 'bijou2', value: 'data.bijou2', title: 'Bijou 2', align: 'center' },
{ key: 'arme', value: 'data.arme', title: 'Arme', align: 'center' },
{ key: 'mainGauche', value: 'data.mainGauche', title: 'Main Gauche', align: 'center' },
{ key: 'relique', value: 'data.relique', title: 'Relique / Distance', align: 'center' },
{ value: 'totalBIS', title: 'Total BIS', align: 'center' },
{ value: 'tokenHM', title: 'Tokens HM', align: 'center' }
]
onMounted(async () => {
await getBisList()
})
const getBisList = async () => {
bisList.length = 0
const result = await httpRequest('/bisList', { method: 'GET' }, false, true)
bisList.push(...result.bis)
}
const colorRowItem = (row) => {
const index = selected.value.findIndex(s => s.name === row.item.name && s.classe === row.item.classe && s.tokenHM === row.item.tokenHM)
if (index > -1) return { class: 'select-row' }
}
</script>
<template>
<v-container fluid>
<v-data-table v-model="selected" class="datatable-height" show-select return-object :headers="headers" :items="bisList" :items-per-page="itemsPerPage" density="compact" :row-props="colorRowItem">
<template #item.name="{ item }">
<span :class="`color-${item.classe}`">{{ item.name }}</span>
</template>
<template #item.tete="{ value }">
<v-icon v-if="value === 0" icon="mdi-close" color="error" />
<v-icon v-else icon="mdi-check" color="success" />
<span v-if="value > 1">{{ value }}</span>
</template>
<template #item.cou="{ value }">
<v-icon v-if="value === 0" icon="mdi-close" color="error" />
<v-icon v-else icon="mdi-check" color="success" />
<span v-if="value > 1">{{ value }}</span>
</template>
<template #item.epaules="{ value }">
<v-icon v-if="value === 0" icon="mdi-close" color="error" />
<v-icon v-else icon="mdi-check" color="success" />
<span v-if="value > 1">{{ value }}</span>
</template>
<template #item.dos="{ value }">
<v-icon v-if="value === 0" icon="mdi-close" color="error" />
<v-icon v-else icon="mdi-check" color="success" />
<span v-if="value > 1">{{ value }}</span>
</template>
<template #item.torse="{ value }">
<v-icon v-if="value === 0" icon="mdi-close" color="error" />
<v-icon v-else icon="mdi-check" color="success" />
<span v-if="value > 1">{{ value }}</span>
</template>
<template #item.poignets="{ value }">
<v-icon v-if="value === 0" icon="mdi-close" color="error" />
<v-icon v-else icon="mdi-check" color="success" />
<span v-if="value > 1">{{ value }}</span>
</template>
<template #item.mains="{ value }">
<v-icon v-if="value === 0" icon="mdi-close" color="error" />
<v-icon v-else icon="mdi-check" color="success" />
<span v-if="value > 1">{{ value }}</span>
</template>
<template #item.taille="{ value }">
<v-icon v-if="value === 0" icon="mdi-close" color="error" />
<v-icon v-else icon="mdi-check" color="success" />
<span v-if="value > 1">{{ value }}</span>
</template>
<template #item.jambes="{ value }">
<v-icon v-if="value === 0" icon="mdi-close" color="error" />
<v-icon v-else icon="mdi-check" color="success" />
<span v-if="value > 1">{{ value }}</span>
</template>
<template #item.pied="{ value }">
<v-icon v-if="value === 0" icon="mdi-close" color="error" />
<v-icon v-else icon="mdi-check" color="success" />
<span v-if="value > 1">{{ value }}</span>
</template>
<template #item.doigt1="{ value }">
<v-icon v-if="value === 0" icon="mdi-close" color="error" />
<v-icon v-else icon="mdi-check" color="success" />
<span v-if="value > 1">{{ value }}</span>
</template>
<template #item.doigt2="{ value }">
<v-icon v-if="value === 0" icon="mdi-close" color="error" />
<v-icon v-else icon="mdi-check" color="success" />
<span v-if="value > 1">{{ value }}</span>
</template>
<template #item.bijou1="{ value }">
<v-icon v-if="value === 0" icon="mdi-close" color="error" />
<v-icon v-else icon="mdi-check" color="success" />
<span v-if="value > 1">{{ value }}</span>
</template>
<template #item.bijou2="{ value }">
<v-icon v-if="value === 0" icon="mdi-close" color="error" />
<v-icon v-else icon="mdi-check" color="success" />
<span v-if="value > 1">{{ value }}</span>
</template>
<template #item.arme="{ value }">
<v-icon v-if="value === 0" icon="mdi-close" color="error" />
<v-icon v-else icon="mdi-check" color="success" />
<span v-if="value > 1">{{ value }}</span>
</template>
<template #item.mainGauche="{ value }">
<v-icon v-if="value === 0" icon="mdi-close" color="error" />
<v-icon v-else icon="mdi-check" color="success" />
<span v-if="value > 1">{{ value }}</span>
</template>
<template #item.relique="{ value }">
<v-icon v-if="value === 0" icon="mdi-close" color="error" />
<v-icon v-else icon="mdi-check" color="success" />
<span v-if="value > 1">{{ value }}</span>
</template>
</v-data-table>
</v-container>
</template>

177
src/components/HistoryList.vue

@ -0,0 +1,177 @@
<script setup>
import { ref, reactive, onMounted, computed } from 'vue'
import { httpRequest } from '@/plugins/httpRequest.js'
import { DateTime } from 'luxon'
const itemsPerPage = ref(25)
const addHistoryDialog = ref(false)
const history = ref('')
const errorMessage = ref('')
const formValid = ref(true)
const historyList = reactive([])
const historySelected = ref([])
const deleteHistoryDialog = ref(false)
const isLoading = ref(false)
const search = ref('')
const headers = [
{ value: 'name', title: 'Membre', sortable: true },
{ value: 'fullDate', title: 'Date', align: 'center', sortable: true },
{ value: 'itemID', title: 'Item' },
{ value: 'response', title: 'Reponse', align: 'center' },
{ value: 'votes', title: 'Votes', align: 'center' },
{ value: 'instance', title: 'Instance', align: 'center' },
{ value: 'boss', title: 'Boss', align: 'center' },
{ value: 'equipLoc', title: 'Emplacement', align: 'center' },
{ value: 'note', title: 'Note' }
]
onMounted(async () => {
await getHistory()
})
const isLogged = computed(() => {
if (localStorage.getItem('token')) return true
return false
})
const getHistory = async () => {
historyList.length = 0
const result = await httpRequest('/histories', { method: 'GET' }, false, true)
historyList.push(...result.histories)
}
const addHistory = () => {
addHistoryDialog.value = true
}
const cancelAddHistory = () => {
history.value = ''
errorMessage.value = ''
addHistoryDialog.value = false
}
const submitAddHistory = async () => {
errorMessage.value = ''
formValid.value = true
if (!history.value || history.value === '') {
errorMessage.value = 'Le champ ne peut être vide.'
formValid.value = false
}
if (formValid.value) {
const body = {
history: history.value
}
try {
isLoading.value = true
const result = await httpRequest('/histories', { method: 'POST', body }, true, true)
if (result.success) {
await getHistory()
cancelAddHistory()
} else errorMessage.value = result.message
isLoading.value = false
} catch (e) {
errorMessage.value = e.message
isLoading.value = false
}
}
}
const deleteHistory = () => {
deleteHistoryDialog.value = true
}
const cancelDeleteHistory = () => {
deleteHistoryDialog.value = false
historySelected.value = []
}
const submitDeleteHistory = async () => {
if (historySelected.value.length > 0) {
const histories = []
for (const hist of historySelected.value) {
histories.push(hist.id)
}
try {
isLoading.value = true
const result = await httpRequest( '/deleteHistories', { method: 'POST', body: { histories } }, true, true)
if (result.success) {
await getHistory()
cancelDeleteHistory()
} else errorMessage.value = result.message
isLoading.value = false
} catch (e) {
errorMessage.value = e.message
isLoading.value = false
}
}
}
</script>
<template>
<v-data-table class="datatable-height" v-model="historySelected" filter-keys="name" :search="search" :headers="headers" :items="historyList" :items-per-page="itemsPerPage" density="compact" return-object show-select>
<template #top>
<v-toolbar flat>
<v-text-field v-model="search" class="ml-5" label="Filtrer un membre" prepend-inner-icon="mdi-magnify" variant="outlined" hide-details single-line density="compact" />
<v-spacer />
<v-btn v-if="isLogged" class="mr-5" :disabled="historySelected.length === 0" prepend-icon="mdi-trash-can-outline" color="error" variant="flat" text="Supprimer" @click="deleteHistory" />
<v-btn v-if="isLogged" class="mr-5" prepend-icon="mdi-plus-circle" variant="outlined" color="primary" text="Ajouter" @click="addHistory" />
</v-toolbar>
</template>
<template #item.name="{ item }">
<span :class="`color-${item.class}`">{{ item.name }}</span>
</template>
<template #item.fullDate="{ value }">
{{ DateTime.fromMillis(value).toUTC().toFormat('dd/MM/yyyy HH:mm') }}
</template>
<template #item.itemID="{ item }">
<a :href="`https://wowhead.com/cata/fr/item=${item.itemID}`" target="_blank">{{ item.itemName }}</a>
</template>
</v-data-table>
<v-dialog max-width="500" persistent v-model="addHistoryDialog">
<v-card title="Ajout d'historique">
<v-card-text>
<v-form>
<v-container>
<v-row>
<v-col cols="12">
<v-textarea v-model="history" rows="15" bg-color="white" clearable label="Historique" variant="outlined" no-resize />
</v-col>
</v-row>
<v-row>
<v-col cols="12">
<span class="text-error">{{ errorMessage }}</span>
</v-col>
</v-row>
</v-container>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn prepend-icon="mdi-cancel" text="Annuler" variant="outlined" color="warning" @click="cancelAddHistory" />
<v-btn :loading="isLoading" prepend-icon="mdi-content-save-plus-outline" text="Enregistrer" variant="outlined" color="primary" @click="submitAddHistory" />
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog max-width="400" persistent v-model="deleteHistoryDialog">
<v-card title="Suppression d'historique">
<v-card-text v-if="historySelected.length">
Êtes vous sûr de vouloir supprimer ces {{ historySelected.length }} historiques ? Cette action est irreversible.
<span class="text-error">{{ errorMessage }}</span>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn prepend-icon="mdi-cancel" text="Annuler" variant="outlined" color="warning" @click="cancelDeleteHistory" />
<v-btn :loading="isLoading" prepend-icon="mdi-content-save-plus-outline" text="Confirmer" variant="outlined" color="primary" @click="submitDeleteHistory" />
</v-card-actions>
</v-card>
</v-dialog>
</template>

236
src/components/RosterList.vue

@ -0,0 +1,236 @@
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { httpRequest } from '@/plugins/httpRequest.js'
const itemsPerPage = ref(40)
const addMemberDialog = ref(false)
const rosterList = reactive([])
const player = ref('')
const errorMessagePlayer = ref('')
const classe = ref(null)
const errorMessageClasse = ref('')
const role = ref(null)
const errorMessageRole = ref('')
const formValid = ref(true)
const errorMessage = ref('')
const itemToDelete = ref(null)
const deleteMemberDialog = ref(false)
const classList = [
{ title: 'Chaman', value: 'SHAMAN' },
{ title: 'Chasseur', value: 'HUNTER' },
{ title: 'Chevalier de la mort', value: 'DEATHKNIGHT' },
{ title: 'Démoniste', value: 'WARLOCK' },
{ title: 'Druide', value: 'DRUID' },
{ title: 'Guerrier', value: 'WARRIOR' },
{ title: 'Mage', value: 'MAGE' },
{ title: 'Paladin', value: 'PALADIN' },
{ title: 'Prêtre', value: 'PRIEST' },
{ title: 'Voleur', value: 'ROGUE' }
]
const isLogged = computed(() => {
if (localStorage.getItem('token')) return true
return false
})
const roleList = computed(() => {
switch (classe.value) {
case 'SHAMAN':
return ['Melee', 'Ranged', 'Healer']
case 'PRIEST':
return ['Ranged', 'Healer']
case 'HUNTER':
case 'WARLOCK':
case 'MAGE':
return ['Ranged']
case 'DEATHKNIGHT':
case 'WARRIOR':
return ['Melee', 'Tank']
case 'PALADIN':
return ['Melee', 'Tank', 'Healer']
case 'ROGUE':
return ['Melee']
case 'DRUID':
default:
return ['Melee', 'Tank', 'Ranged', 'Healer']
}
})
const headers = [
{ value: 'name', title: 'Membre', sortable: true },
{ value: 'classe', title: 'Classe', sortable: true },
{ value: 'role', title: 'Role', sortable: true },
{ value: 'actions', title: '' }
]
onMounted(async () => {
await getRoster()
})
const getRoster = async () => {
rosterList.length = 0
const result = await httpRequest('/rosters', { method: 'GET' }, false, true)
rosterList.push(...result.rosters)
}
const formatClasseName = (classe) => {
const item = classList.find(c => c.value === classe)
if (item) return item.title
return classe
}
const addMember = () => {
addMemberDialog.value = true
}
const cancelAddMember = () => {
resetAddMember()
addMemberDialog.value = false
}
const resetAddMember = () => {
player.value = ''
classe.value = null
role.value = null
formValid.value = true
errorMessage.value = ''
errorMessagePlayer.value = ''
errorMessageClasse.value = ''
errorMessageRole.value = ''
}
const submitAddMember = async () => {
errorMessagePlayer.value = ''
errorMessageClasse.value = ''
errorMessageRole.value = ''
formValid.value = true
if (!player.value || player.value === '') {
errorMessagePlayer.value = 'Le nom du personnage est requis.'
formValid.value = false
}
if (!classe.value || classe.value === '') {
errorMessageClasse.value = 'La classe du personnage est requise.'
formValid.value = false
}
if (!role.value || role.value === '' || !roleList.value.includes(role.value)) {
errorMessageRole.value = 'Le role du personnage est requis, et doit être valide'
formValid.value = false
}
if (formValid.value) {
const body = {
name: player.value,
classe: classe.value,
role: role.value
}
try {
const result = await httpRequest('/rosters', { method: 'POST', body }, true, true)
if (result.success) {
await getRoster()
cancelAddMember()
} else errorMessage.value = result.message
} catch (e) {
errorMessage.value = e.message
}
}
}
const remove = (item) => {
itemToDelete.value = item
deleteMemberDialog.value = true
}
const cancelDeleteMember = () => {
deleteMemberDialog.value = false
itemToDelete.value = null
}
const submitDeleteMember = async () => {
if (itemToDelete.value) {
try {
const result = await httpRequest( `/rosters/${itemToDelete.value.name}`, { method: 'DELETE' }, true, true)
if (result.success) {
await getRoster()
cancelDeleteMember()
} else errorMessage.value = result.message
} catch (e) {
errorMessage.value = e.message
}
}
}
</script>
<template>
<v-container>
<v-data-table class="datatable-height" :headers="headers" :items="rosterList" :items-per-page="itemsPerPage" density="compact">
<template v-if="isLogged" #top>
<v-toolbar flat>
<v-spacer />
<v-btn class="mr-5" prepend-icon="mdi-plus-circle" variant="outlined" color="primary" text="Ajouter" @click="addMember" />
</v-toolbar>
</template>
<template #item.name="{ item }">
<span :class="`color-${item.classe}`">{{ item.name }}</span>
</template>
<template #item.classe="{ value }">
<span :class="`color-${value}`">{{ formatClasseName(value) }}</span>
</template>
<template v-if="isLogged" #item.actions="{ item }">
<v-icon class="pointer" color="error" icon="mdi-delete-outline" @click="remove(item)" />
</template>
</v-data-table>
</v-container>
<v-dialog max-width="500" persistent v-model="addMemberDialog">
<v-card title="Ajout d'un membre au roster">
<v-card-text>
<v-form>
<v-container>
<v-row>
<v-col cols="12">
<v-text-field v-model="player" label="Nom" density="compact" :error-messages="errorMessagePlayer" required />
</v-col>
</v-row>
<v-row>
<v-col cols="12">
<v-select v-model="classe" label="Classe" density="compact" :items="classList" :error-messages="errorMessageClasse" required />
</v-col>
</v-row>
<v-row>
<v-col cols="12">
<v-select v-model="role" label="Role" density="compact" :items="roleList" :error-messages="errorMessageRole" required />
</v-col>
</v-row>
<v-row>
<v-col cols="12">
<span class="text-error">{{ errorMessage }}</span>
</v-col>
</v-row>
</v-container>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn prepend-icon="mdi-cancel" text="Annuler" variant="outlined" color="warning" @click="cancelAddMember" />
<v-btn prepend-icon="mdi-content-save-plus-outline" text="Enregistrer" variant="outlined" color="primary" @click="submitAddMember" />
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog max-width="400" persistent v-model="deleteMemberDialog">
<v-card title="Suppression d'un membre du roster">
<v-card-text v-if="itemToDelete">
Êtes vous sûr de vouloir supprimer <b>"{{ itemToDelete.name }}"</b> du roster ? Cette action est irreversible.
<span class="text-error">{{ errorMessage }}</span>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn prepend-icon="mdi-cancel" text="Annuler" variant="outlined" color="warning" @click="cancelDeleteMember" />
<v-btn prepend-icon="mdi-content-save-plus-outline" text="Confirmer" variant="outlined" color="primary" @click="submitDeleteMember" />
</v-card-actions>
</v-card>
</v-dialog>
</template>

28
src/main.js

@ -0,0 +1,28 @@
import { createApp } from 'vue'
import App from './App.vue'
import 'vuetify/styles'
import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'
import { aliases, mdi } from 'vuetify/iconsets/mdi'
import '@mdi/font/css/materialdesignicons.css'
import './assets/main.css'
const vuetify = createVuetify({
theme: {
defaultTheme: 'dark'
},
components,
directives,
icons: {
defaultSet: 'mdi',
aliases,
sets: {
mdi
}
}
})
createApp(App).use(vuetify).mount('#app')

29
src/plugins/httpRequest.js

@ -0,0 +1,29 @@
const serverUrl = import.meta.env.VITE_SERVER_SUIVI_URL || 'http://localhost:3201'
export const httpRequest = async function(url, { method = 'GET', body }, authentified = false, jsonRequest = false) {
const headers = new Headers()
if (body) {
headers.append('accept', 'application/json')
headers.append('content-type', 'application/json')
}
if (authentified) {
const token = localStorage.getItem('token')
headers.append('authorization', `Bearer ${token}`)
}
const init = { method, headers }
if (body) init.body = JSON.stringify(body);
let _url = url;
if (!_url.startsWith(serverUrl)) _url = `${serverUrl}${url}`;
const res = await fetch(_url, init);
if (res.status === 401) {
localStorage.removeItem('token');
// router.push({ name: 'login' });
} else {
if (jsonRequest) {
const json = await res.json();
if (res.ok) return json;
else return Promise.reject(json);
} else return res;
}
}

19
vite.config.js

@ -0,0 +1,19 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
],
server: {
port: 3200
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
Loading…
Cancel
Save