commit
b93ee63584
19 changed files with 1991 additions and 0 deletions
@ -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 |
||||||
@ -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" |
||||||
@ -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 |
||||||
|
``` |
||||||
@ -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;' |
||||||
@ -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> |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"paths": { |
||||||
|
"@/*": ["./src/*"] |
||||||
|
} |
||||||
|
}, |
||||||
|
"exclude": ["node_modules", "dist"] |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
@ -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" |
||||||
|
} |
||||||
|
} |
||||||
|
After Width: | Height: | Size: 4.2 KiB |
@ -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> |
||||||
@ -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; |
||||||
|
} |
||||||
@ -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> |
||||||
@ -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> |
||||||
@ -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> |
||||||
@ -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') |
||||||
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
@ -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…
Reference in new issue