Merge branch 'main' of github.com:jcalixte/tps
This commit is contained in:
@@ -45,7 +45,7 @@
|
|||||||
<li class="takt focusable">Takt</li>
|
<li class="takt focusable">Takt</li>
|
||||||
<li class="one-piece-flow focusable">One piece flow</li>
|
<li class="one-piece-flow focusable">One piece flow</li>
|
||||||
<li class="pull-system focusable">
|
<li class="pull-system focusable">
|
||||||
<a href="/pull-system.html">Pull system</a>
|
<a href="/pull-system">Pull system</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.1.11",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
"@vueuse/core": "^12.2.0",
|
"@vueuse/core": "^12.2.0",
|
||||||
|
"chart.js": "^4.5.0",
|
||||||
"chart.xkcd": "^1.1.15",
|
"chart.xkcd": "^1.1.15",
|
||||||
"comlink": "^4.4.2",
|
"comlink": "^4.4.2",
|
||||||
"daisyui": "^5.0.50",
|
"daisyui": "^5.0.50",
|
||||||
@@ -26,6 +27,7 @@
|
|||||||
"random-js": "^2.1.0",
|
"random-js": "^2.1.0",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.11",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
|
"vue-chartjs": "^5.3.2",
|
||||||
"vue-starport": "^0.4.0"
|
"vue-starport": "^0.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
30
pnpm-lock.yaml
generated
30
pnpm-lock.yaml
generated
@@ -14,6 +14,9 @@ importers:
|
|||||||
'@vueuse/core':
|
'@vueuse/core':
|
||||||
specifier: ^12.2.0
|
specifier: ^12.2.0
|
||||||
version: 12.2.0(typescript@5.7.2)
|
version: 12.2.0(typescript@5.7.2)
|
||||||
|
chart.js:
|
||||||
|
specifier: ^4.5.0
|
||||||
|
version: 4.5.0
|
||||||
chart.xkcd:
|
chart.xkcd:
|
||||||
specifier: ^1.1.15
|
specifier: ^1.1.15
|
||||||
version: 1.1.15
|
version: 1.1.15
|
||||||
@@ -41,6 +44,9 @@ importers:
|
|||||||
vue:
|
vue:
|
||||||
specifier: ^3.5.13
|
specifier: ^3.5.13
|
||||||
version: 3.5.13(typescript@5.7.2)
|
version: 3.5.13(typescript@5.7.2)
|
||||||
|
vue-chartjs:
|
||||||
|
specifier: ^5.3.2
|
||||||
|
version: 5.3.2(chart.js@4.5.0)(vue@3.5.13(typescript@5.7.2))
|
||||||
vue-starport:
|
vue-starport:
|
||||||
specifier: ^0.4.0
|
specifier: ^0.4.0
|
||||||
version: 0.4.0(typescript@5.7.2)
|
version: 0.4.0(typescript@5.7.2)
|
||||||
@@ -408,6 +414,9 @@ packages:
|
|||||||
'@jridgewell/trace-mapping@0.3.29':
|
'@jridgewell/trace-mapping@0.3.29':
|
||||||
resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==}
|
resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==}
|
||||||
|
|
||||||
|
'@kurkle/color@0.3.4':
|
||||||
|
resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==}
|
||||||
|
|
||||||
'@parcel/watcher-android-arm64@2.5.1':
|
'@parcel/watcher-android-arm64@2.5.1':
|
||||||
resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
|
resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
@@ -889,6 +898,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==}
|
resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
chart.js@4.5.0:
|
||||||
|
resolution: {integrity: sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==}
|
||||||
|
engines: {pnpm: '>=8'}
|
||||||
|
|
||||||
chart.xkcd@1.1.15:
|
chart.xkcd@1.1.15:
|
||||||
resolution: {integrity: sha512-FPpNCkaVSPyB6GIb9eXKEAjKSnr6hA7blzQFUeTkZKvSr9wcg1eSK73V6APgfrzkDDK7X+iVtLXJXIGPrCFj8g==}
|
resolution: {integrity: sha512-FPpNCkaVSPyB6GIb9eXKEAjKSnr6hA7blzQFUeTkZKvSr9wcg1eSK73V6APgfrzkDDK7X+iVtLXJXIGPrCFj8g==}
|
||||||
|
|
||||||
@@ -1387,6 +1400,12 @@ packages:
|
|||||||
jsdom:
|
jsdom:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
vue-chartjs@5.3.2:
|
||||||
|
resolution: {integrity: sha512-NrkbRRoYshbXbWqJkTN6InoDVwVb90C0R7eAVgMWcB9dPikbruaOoTFjFYHE/+tNPdIe6qdLCDjfjPHQ0fw4jw==}
|
||||||
|
peerDependencies:
|
||||||
|
chart.js: ^4.1.1
|
||||||
|
vue: ^3.0.0-0 || ^2.7.0
|
||||||
|
|
||||||
vue-demi@0.14.10:
|
vue-demi@0.14.10:
|
||||||
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
|
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -1602,6 +1621,8 @@ snapshots:
|
|||||||
'@jridgewell/resolve-uri': 3.1.2
|
'@jridgewell/resolve-uri': 3.1.2
|
||||||
'@jridgewell/sourcemap-codec': 1.5.0
|
'@jridgewell/sourcemap-codec': 1.5.0
|
||||||
|
|
||||||
|
'@kurkle/color@0.3.4': {}
|
||||||
|
|
||||||
'@parcel/watcher-android-arm64@2.5.1':
|
'@parcel/watcher-android-arm64@2.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -2020,6 +2041,10 @@ snapshots:
|
|||||||
loupe: 3.1.3
|
loupe: 3.1.3
|
||||||
pathval: 2.0.0
|
pathval: 2.0.0
|
||||||
|
|
||||||
|
chart.js@4.5.0:
|
||||||
|
dependencies:
|
||||||
|
'@kurkle/color': 0.3.4
|
||||||
|
|
||||||
chart.xkcd@1.1.15:
|
chart.xkcd@1.1.15:
|
||||||
dependencies:
|
dependencies:
|
||||||
d3-axis: 1.0.12
|
d3-axis: 1.0.12
|
||||||
@@ -2507,6 +2532,11 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- terser
|
- terser
|
||||||
|
|
||||||
|
vue-chartjs@5.3.2(chart.js@4.5.0)(vue@3.5.13(typescript@5.7.2)):
|
||||||
|
dependencies:
|
||||||
|
chart.js: 4.5.0
|
||||||
|
vue: 3.5.13(typescript@5.7.2)
|
||||||
|
|
||||||
vue-demi@0.14.10(vue@3.5.13(typescript@5.7.2)):
|
vue-demi@0.14.10(vue@3.5.13(typescript@5.7.2)):
|
||||||
dependencies:
|
dependencies:
|
||||||
vue: 3.5.13(typescript@5.7.2)
|
vue: 3.5.13(typescript@5.7.2)
|
||||||
|
|||||||
93
src/modules/5s/BoardGamePerformance.vue
Normal file
93
src/modules/5s/BoardGamePerformance.vue
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Chart as ChartJS, registerables } from 'chart.js'
|
||||||
|
import { useBoardGameStore } from '@/modules/5s/board-game-store'
|
||||||
|
import { toDuration, toSeconds } from '@/modules/5s/utils'
|
||||||
|
import { getNatural } from '@/utils'
|
||||||
|
import { _ } from '@faker-js/faker/dist/airline-D6ksJFwG'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import { Bar } from 'vue-chartjs'
|
||||||
|
|
||||||
|
ChartJS.register(...registerables)
|
||||||
|
|
||||||
|
const boardGameStore = useBoardGameStore()
|
||||||
|
|
||||||
|
const duration = ref<string | null>(null)
|
||||||
|
|
||||||
|
const last10Perfs = computed(() =>
|
||||||
|
[...boardGameStore.meta.perfs].slice(-10).reverse()
|
||||||
|
)
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
duration.value = boardGameStore.meta.start
|
||||||
|
? toDuration(
|
||||||
|
new Date(boardGameStore.meta.start),
|
||||||
|
boardGameStore.meta.end ? new Date(boardGameStore.meta.end) : new Date()
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
}, 1000)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="board-game-performance">
|
||||||
|
<p class="numeric">{{ duration }}</p>
|
||||||
|
|
||||||
|
<template v-if="boardGameStore.meta.perfs.length > 0">
|
||||||
|
<h3>Last performances</h3>
|
||||||
|
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Duration</th>
|
||||||
|
<th>Board Games</th>
|
||||||
|
<th>Time / board game</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="perf in last10Perfs">
|
||||||
|
<td class="numeric">
|
||||||
|
{{ toDuration(new Date(perf.start), new Date(perf.end)) }}
|
||||||
|
</td>
|
||||||
|
<td class="numeric">
|
||||||
|
{{ perf.totalGames }}
|
||||||
|
</td>
|
||||||
|
<td class="numeric">
|
||||||
|
{{
|
||||||
|
getNatural(
|
||||||
|
toSeconds(new Date(perf.start), new Date(perf.end)),
|
||||||
|
perf.totalGames
|
||||||
|
)
|
||||||
|
}}s
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Progression</h3>
|
||||||
|
|
||||||
|
<Bar
|
||||||
|
id="my-chart-id"
|
||||||
|
:data="{
|
||||||
|
labels: boardGameStore.meta.perfs.map((_, i) => `Round #${i + 1}`),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Time per board game (in s)',
|
||||||
|
data: boardGameStore.meta.perfs.map((perf) =>
|
||||||
|
toSeconds(new Date(perf.start), new Date(perf.end))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.board-game-performance {
|
||||||
|
.numeric {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,44 +1,92 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useBoardGameStore } from '@/modules/5s/board-game-store'
|
import { useBoardGameStore } from '@/modules/5s/board-game-store'
|
||||||
|
import { shuffleArray } from '@/utils'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
const boardGameStore = useBoardGameStore()
|
const boardGameStore = useBoardGameStore()
|
||||||
|
|
||||||
|
const isSeiriActivated = computed(() => boardGameStore.sUsed.includes('seiri'))
|
||||||
const isSeitonActivated = computed(() =>
|
const isSeitonActivated = computed(() =>
|
||||||
boardGameStore.sUsed.includes('seiton')
|
boardGameStore.sUsed.includes('seiton')
|
||||||
)
|
)
|
||||||
|
// const isSeisoActivated = computed(() => boardGameStore.sUsed.includes('seiso'))
|
||||||
|
|
||||||
const rawTools = boardGameStore.tools
|
const neededTools = computed(
|
||||||
.map((t) => `${t.name} (${t.alias})`)
|
() =>
|
||||||
.join(', ')
|
new Set(
|
||||||
|
boardGameStore.boardGames
|
||||||
|
.flatMap((g) => g.parts)
|
||||||
|
.flatMap((p) => p.tasks)
|
||||||
|
.flatMap((t) => t.tools)
|
||||||
|
.map((t) => t.id)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
const tools = computed(() => {
|
||||||
|
const toolsToUse = isSeiriActivated.value
|
||||||
|
? boardGameStore.tools.filter((t) => neededTools.value.has(t.id))
|
||||||
|
: boardGameStore.tools
|
||||||
|
|
||||||
|
return isSeitonActivated.value
|
||||||
|
? [...toolsToUse].sort(
|
||||||
|
(a, b) =>
|
||||||
|
(boardGameStore.countUsedTools[b.id] || 0) -
|
||||||
|
(boardGameStore.countUsedTools[a.id] || 0)
|
||||||
|
)
|
||||||
|
: toolsToUse
|
||||||
|
})
|
||||||
|
|
||||||
|
const toolsToDisplay = computed(() =>
|
||||||
|
shuffleArray(tools.value.map((t) => `${t.name} (ref: ${t.reference})`)).join(
|
||||||
|
', '
|
||||||
|
)
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<aside class="board-game-tools">
|
<div class="board-game-tools prose">
|
||||||
<h2>Tools</h2>
|
|
||||||
<div class="overflow-x-auto" v-if="isSeitonActivated">
|
<div class="overflow-x-auto" v-if="isSeitonActivated">
|
||||||
<table class="table table-zebra">
|
<table class="table table-md">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Tool</th>
|
<th>Tool</th>
|
||||||
<th>Alias</th>
|
<th>Reference</th>
|
||||||
|
<th>Used</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<transition-group name="list" tag="tbody">
|
||||||
<tr v-for="tool in boardGameStore.tools" :key="tool.alias">
|
<tr v-for="tool in tools" :key="tool.reference">
|
||||||
<td>{{ tool.name }}</td>
|
<td>{{ tool.name }}</td>
|
||||||
<td>{{ tool.alias }}</td>
|
<td class="numeric">{{ tool.reference }}</td>
|
||||||
|
<td class="numeric count">
|
||||||
|
{{ boardGameStore.countUsedTools[tool.id] }}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</transition-group>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
{{ rawTools }}
|
{{ toolsToDisplay }}
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.board-game-tools {
|
.board-game-tools {
|
||||||
flex: 1;
|
.count {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-move, /* apply transition to moving elements */
|
||||||
|
.list-enter-active,
|
||||||
|
.list-leave-active {
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-enter-from,
|
||||||
|
.list-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(30px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useBoardGameStore } from '@/modules/5s/board-game-store'
|
import { useBoardGameStore } from '@/modules/5s/board-game-store'
|
||||||
|
import BoardGamePerformance from '@/modules/5s/BoardGamePerformance.vue'
|
||||||
|
import BoardGameToolbox from '@/modules/5s/BoardGameToolbox.vue'
|
||||||
import BoardGameToolbox from '@/modules/5s/Toolbox.vue'
|
import BoardGameToolbox from '@/modules/5s/Toolbox.vue'
|
||||||
import { _5S, is5S } from '@/modules/5s/types/5s'
|
import { _5S, is5S } from '@/modules/5s/types/5s'
|
||||||
import { toDuration } from '@/modules/5s/utils'
|
import { _5S, is5S } from '@/modules/5s/types/5s'
|
||||||
import { ref, toValue } from 'vue'
|
import { onMounted, ref, toValue } from 'vue'
|
||||||
|
|
||||||
const userInput = ref('')
|
const userInput = ref('')
|
||||||
const mode = ref<_5S | null>(null)
|
const mode = ref<_5S | null>(null)
|
||||||
@@ -18,6 +20,11 @@ setInterval(() => {
|
|||||||
)
|
)
|
||||||
: null
|
: null
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
onMounted(() => {
|
||||||
|
boardGameStore.initGame()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
const lastInput = toValue(userInput)
|
const lastInput = toValue(userInput)
|
||||||
@@ -43,23 +50,34 @@ const submit = () => {
|
|||||||
boardGameStore.activateS(command)
|
boardGameStore.activateS(command)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// d for debug
|
||||||
|
if (command === 'd') {
|
||||||
|
boardGameStore.increment()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="board-game-workshop prose">
|
<header v-if="!boardGameStore.currentBoardGame">
|
||||||
<BoardGameToolbox />
|
<button
|
||||||
<div class="main">
|
class="btn btn-primary"
|
||||||
<h2>Workshop</h2>
|
v-if="!boardGameStore.currentBoardGame"
|
||||||
<button
|
@click="boardGameStore.initGame"
|
||||||
class="btn"
|
>
|
||||||
v-if="!boardGameStore.currentBoardGame"
|
start
|
||||||
@click="boardGameStore.initGame"
|
</button>
|
||||||
>
|
</header>
|
||||||
start
|
<div v-else class="board-game-workshop">
|
||||||
</button>
|
<aside class="prose">
|
||||||
|
<h2>Toolbox</h2>
|
||||||
|
|
||||||
<div v-if="boardGameStore.currentBoardGame">
|
<BoardGameToolbox />
|
||||||
|
</aside>
|
||||||
|
<div class="main prose">
|
||||||
|
<h2 class="title">Workshop</h2>
|
||||||
|
|
||||||
|
<div>
|
||||||
<form @submit.prevent="submit">
|
<form @submit.prevent="submit">
|
||||||
<input type="text" v-model="userInput" autofocus />
|
<input type="text" v-model="userInput" autofocus />
|
||||||
</form>
|
</form>
|
||||||
@@ -85,11 +103,6 @@ const submit = () => {
|
|||||||
{{ part.name }}
|
{{ part.name }}
|
||||||
</span>
|
</span>
|
||||||
<template v-if="partIndex === boardGameStore.currentPartIndex">
|
<template v-if="partIndex === boardGameStore.currentPartIndex">
|
||||||
<div class="inline-grid *:[grid-area:1/1]">
|
|
||||||
<div class="status status-primary animate-ping"></div>
|
|
||||||
<div class="status status-primary"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ol>
|
<ol>
|
||||||
<li
|
<li
|
||||||
v-for="(task, taskIndex) in boardGameStore.currentPart
|
v-for="(task, taskIndex) in boardGameStore.currentPart
|
||||||
@@ -110,9 +123,9 @@ const submit = () => {
|
|||||||
boardGameStore.currentTask
|
boardGameStore.currentTask
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<div class="inline-grid *:[grid-area:1/1]">
|
<div class="inline-grid *:[grid-area:1/1] ml-2">
|
||||||
<div class="status status-primary animate-ping"></div>
|
<div class="status status-info animate-ping"></div>
|
||||||
<div class="status status-primary"></div>
|
<div class="status status-info"></div>
|
||||||
</div>
|
</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li
|
<li
|
||||||
@@ -137,24 +150,9 @@ const submit = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<aside class="performance prose">
|
||||||
<aside
|
|
||||||
class="performance"
|
|
||||||
v-if="duration !== null || boardGameStore.meta.perfs.length > 0"
|
|
||||||
>
|
|
||||||
<h2>Performance</h2>
|
<h2>Performance</h2>
|
||||||
|
<BoardGamePerformance />
|
||||||
<p>{{ duration }}</p>
|
|
||||||
|
|
||||||
<template v-if="boardGameStore.meta.perfs.length > 0">
|
|
||||||
<h3>Last performances</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li v-for="perf in boardGameStore.meta.perfs">
|
|
||||||
{{ toDuration(new Date(perf[0]), new Date(perf[1])) }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</template>
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -163,14 +161,9 @@ const submit = () => {
|
|||||||
@import url('https://fonts.googleapis.com/css2?family=Google+Sans+Code&display=swap');
|
@import url('https://fonts.googleapis.com/css2?family=Google+Sans+Code&display=swap');
|
||||||
|
|
||||||
.board-game-workshop {
|
.board-game-workshop {
|
||||||
flex: 1;
|
|
||||||
font-family: 'Google Sans Code', monospace;
|
|
||||||
font-optical-sizing: auto;
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: normal;
|
|
||||||
font-size: 14px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4rem;
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
font-family: 'Google Sans Code', monospace;
|
font-family: 'Google Sans Code', monospace;
|
||||||
@@ -180,6 +173,10 @@ const submit = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
form {
|
form {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@@ -189,14 +186,23 @@ form {
|
|||||||
color: green;
|
color: green;
|
||||||
}
|
}
|
||||||
|
|
||||||
aside {
|
aside,
|
||||||
|
.aside {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
flex: 2;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { boardGames } from '@/modules/5s/types/board-games'
|
|||||||
import { tools } from '@/modules/5s/types/tools'
|
import { tools } from '@/modules/5s/types/tools'
|
||||||
import { BoardGame, Part, Task, Tool } from '@/modules/5s/types/workshop'
|
import { BoardGame, Part, Task, Tool } from '@/modules/5s/types/workshop'
|
||||||
import { toDuration } from '@/modules/5s/utils'
|
import { toDuration } from '@/modules/5s/utils'
|
||||||
import { randomAlias } from '@/utils'
|
import { accumulate, randomAlias } from '@/utils'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
@@ -17,10 +17,23 @@ type State = {
|
|||||||
meta: {
|
meta: {
|
||||||
start: string | null
|
start: string | null
|
||||||
end: string | null
|
end: string | null
|
||||||
perfs: Array<[string, string]>
|
perfs: Array<{
|
||||||
|
start: string
|
||||||
|
end: string
|
||||||
|
boardGames: BoardGame[]
|
||||||
|
countGames: Record<string, number>
|
||||||
|
totalGames: number
|
||||||
|
}>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const firstDemands = [
|
||||||
|
boardGames[0],
|
||||||
|
boardGames[0]
|
||||||
|
// boardGames[0],
|
||||||
|
// boardGames[0]
|
||||||
|
]
|
||||||
|
|
||||||
export const useBoardGameStore = defineStore('board-game', {
|
export const useBoardGameStore = defineStore('board-game', {
|
||||||
state: (): State => ({
|
state: (): State => ({
|
||||||
tools: [],
|
tools: [],
|
||||||
@@ -38,12 +51,11 @@ export const useBoardGameStore = defineStore('board-game', {
|
|||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
initGame() {
|
initGame() {
|
||||||
// this.boardGames = [boardGames[0], boardGames[1]]
|
|
||||||
this.tools = tools.map((t) => ({
|
this.tools = tools.map((t) => ({
|
||||||
...t,
|
...t,
|
||||||
alias: randomAlias()
|
reference: randomAlias()
|
||||||
}))
|
}))
|
||||||
this.boardGames = [boardGames[0]]
|
this.boardGames = firstDemands
|
||||||
this.currentBoardGameIndex = 0
|
this.currentBoardGameIndex = 0
|
||||||
this.currentPartIndex = 0
|
this.currentPartIndex = 0
|
||||||
this.currentTaskIndex = 0
|
this.currentTaskIndex = 0
|
||||||
@@ -55,7 +67,7 @@ export const useBoardGameStore = defineStore('board-game', {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const tool = this.tools.find((t) => t.alias === alias)
|
const tool = this.tools.find((t) => t.reference === alias)
|
||||||
|
|
||||||
if (!tool) {
|
if (!tool) {
|
||||||
return
|
return
|
||||||
@@ -107,7 +119,17 @@ export const useBoardGameStore = defineStore('board-game', {
|
|||||||
|
|
||||||
// All board games complete
|
// All board games complete
|
||||||
this.meta.end = new Date().toISOString()
|
this.meta.end = new Date().toISOString()
|
||||||
this.meta.perfs = [...this.meta.perfs, [this.meta.start, this.meta.end]]
|
const countGames = accumulate(this.boardGames.map((b) => b.name))
|
||||||
|
this.meta.perfs = [
|
||||||
|
...this.meta.perfs,
|
||||||
|
{
|
||||||
|
start: this.meta.start,
|
||||||
|
end: this.meta.end,
|
||||||
|
boardGames: [...this.boardGames],
|
||||||
|
countGames,
|
||||||
|
totalGames: this.boardGames.length
|
||||||
|
}
|
||||||
|
]
|
||||||
this.currentBoardGameIndex = null
|
this.currentBoardGameIndex = null
|
||||||
this.currentPartIndex = null
|
this.currentPartIndex = null
|
||||||
this.currentTaskIndex = null
|
this.currentTaskIndex = null
|
||||||
@@ -156,6 +178,50 @@ export const useBoardGameStore = defineStore('board-game', {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return toDuration(new Date(this.meta.start), new Date(this.meta.end))
|
return toDuration(new Date(this.meta.start), new Date(this.meta.end))
|
||||||
|
},
|
||||||
|
countUsedTools(): Record<string, number> {
|
||||||
|
const metaToolIds = this.meta.perfs
|
||||||
|
.flatMap((p) => p.boardGames)
|
||||||
|
.flatMap((b) => b.parts)
|
||||||
|
.flatMap((p) => p.tasks)
|
||||||
|
.flatMap((t) => t.tools)
|
||||||
|
.map((t) => t.id)
|
||||||
|
|
||||||
|
if (
|
||||||
|
!this.meta.start ||
|
||||||
|
!this.currentTask ||
|
||||||
|
!this.currentPart ||
|
||||||
|
!this.currentBoardGame ||
|
||||||
|
this.currentTaskIndex === null ||
|
||||||
|
this.currentPartIndex === null ||
|
||||||
|
this.currentBoardGameIndex === null
|
||||||
|
) {
|
||||||
|
return accumulate(metaToolIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentBoardGameIndex = this.currentBoardGameIndex
|
||||||
|
const currentPartIndex = this.currentPartIndex
|
||||||
|
const currentTaskIndex = this.currentTaskIndex
|
||||||
|
|
||||||
|
const toolIds = this.boardGames
|
||||||
|
.filter((_, i) => i <= currentBoardGameIndex)
|
||||||
|
.flatMap((b, boardIndex) =>
|
||||||
|
boardIndex === currentBoardGameIndex
|
||||||
|
? b.parts.filter((_, i) => i <= currentPartIndex)
|
||||||
|
: b.parts
|
||||||
|
)
|
||||||
|
.flatMap((p, partIndex) =>
|
||||||
|
partIndex === currentPartIndex
|
||||||
|
? p.tasks.filter((_, i) => i <= currentTaskIndex)
|
||||||
|
: p.tasks
|
||||||
|
)
|
||||||
|
.flatMap((t, taskIndex) =>
|
||||||
|
taskIndex === currentTaskIndex
|
||||||
|
? this.usedTools
|
||||||
|
: t.tools.map((t) => t.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
return accumulate([...metaToolIds, ...toolIds])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { NonEmptyArray } from '@/modules/5s/types/tools'
|
|||||||
export type Tool = {
|
export type Tool = {
|
||||||
name: string
|
name: string
|
||||||
alias: string
|
alias: string
|
||||||
cooldown: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Task = {
|
export type Task = {
|
||||||
@@ -29,14 +28,14 @@ export type BoardGame = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const tools: Tool[] = [
|
export const tools: Tool[] = [
|
||||||
{ name: 'Card Printer', alias: 'card-printer', cooldown: 5 },
|
{ name: 'Card Printer', alias: 'card-printer' },
|
||||||
{ name: 'Miniature Mold', alias: 'mini-mold', cooldown: 15 },
|
{ name: 'Miniature Mold', alias: 'mini-mold' },
|
||||||
{ name: 'Dice Engraver', alias: 'dice-engraver', cooldown: 10 },
|
{ name: 'Dice Engraver', alias: 'dice-engraver' },
|
||||||
{ name: 'Board Cutter', alias: 'board-cutter', cooldown: 8 },
|
{ name: 'Board Cutter', alias: 'board-cutter' },
|
||||||
{ name: 'Rulebook Designer', alias: 'rulebook-dzn', cooldown: 6 },
|
{ name: 'Rulebook Designer', alias: 'rulebook-dzn' },
|
||||||
{ name: 'Box Assembler', alias: 'box-asm', cooldown: 4 },
|
{ name: 'Box Assembler', alias: 'box-asm' },
|
||||||
{ name: 'Component Painter', alias: 'painter', cooldown: 12 },
|
{ name: 'Component Painter', alias: 'painter' },
|
||||||
{ name: 'Lamination Machine', alias: 'laminator', cooldown: 7 }
|
{ name: 'Lamination Machine', alias: 'laminator' }
|
||||||
]
|
]
|
||||||
export type NonEmptyArray<T> = [T, ...T[]]
|
export type NonEmptyArray<T> = [T, ...T[]]
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export const boardGames: BoardGame[] = [
|
|||||||
{
|
{
|
||||||
name: 'Chessboard Engraving',
|
name: 'Chessboard Engraving',
|
||||||
tasks: [
|
tasks: [
|
||||||
{ name: 'Cut the board base', tools: chooseTools('board-cutter') },
|
{ name: 'Cut the board base', tools: chooseTools('cutter') },
|
||||||
{
|
{
|
||||||
name: 'Apply lamination',
|
name: 'Apply lamination',
|
||||||
tools: chooseTools('laminator', 'painter')
|
tools: chooseTools('laminator', 'painter')
|
||||||
@@ -19,12 +19,8 @@ export const boardGames: BoardGame[] = [
|
|||||||
name: 'Piece Creation',
|
name: 'Piece Creation',
|
||||||
tasks: [
|
tasks: [
|
||||||
{
|
{
|
||||||
name: 'Mold pawns',
|
name: 'Mold pieces',
|
||||||
tools: chooseTools('mini-mold', 'painter')
|
tools: chooseTools('mini-mold', 'cutter')
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Engrave royalty',
|
|
||||||
tools: chooseTools('dice-engraver', 'mini-mold')
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Paint pieces',
|
name: 'Paint pieces',
|
||||||
@@ -68,7 +64,7 @@ export const boardGames: BoardGame[] = [
|
|||||||
tasks: [
|
tasks: [
|
||||||
{
|
{
|
||||||
name: 'Cut dungeon tiles',
|
name: 'Cut dungeon tiles',
|
||||||
tools: chooseTools('board-cutter', 'laminator')
|
tools: chooseTools('cutter', 'laminator')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -82,7 +78,7 @@ export const boardGames: BoardGame[] = [
|
|||||||
tasks: [
|
tasks: [
|
||||||
{
|
{
|
||||||
name: 'Print scenario deck',
|
name: 'Print scenario deck',
|
||||||
tools: chooseTools('card-printer', 'rulebook-dzn')
|
tools: chooseTools('card-prisnter', 'rulebook-dzn')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Apply finish',
|
name: 'Apply finish',
|
||||||
@@ -113,7 +109,7 @@ export const boardGames: BoardGame[] = [
|
|||||||
tasks: [
|
tasks: [
|
||||||
{
|
{
|
||||||
name: 'Print map base',
|
name: 'Print map base',
|
||||||
tools: chooseTools('board-cutter', 'laminator')
|
tools: chooseTools('cutter', 'laminator')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Add compass',
|
name: 'Add compass',
|
||||||
@@ -171,7 +167,7 @@ export const boardGames: BoardGame[] = [
|
|||||||
tasks: [
|
tasks: [
|
||||||
{
|
{
|
||||||
name: 'Print jungle layout',
|
name: 'Print jungle layout',
|
||||||
tools: chooseTools('board-cutter', 'painter')
|
tools: chooseTools('cutter', 'painter')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Seal board',
|
name: 'Seal board',
|
||||||
@@ -216,7 +212,7 @@ export const boardGames: BoardGame[] = [
|
|||||||
tasks: [
|
tasks: [
|
||||||
{
|
{
|
||||||
name: 'Cut castle walls',
|
name: 'Cut castle walls',
|
||||||
tools: chooseTools('board-cutter', 'painter')
|
tools: chooseTools('cutter', 'painter')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Reinforce walls',
|
name: 'Reinforce walls',
|
||||||
|
|||||||
@@ -1,18 +1,51 @@
|
|||||||
import { Tool } from '@/modules/5s/types/workshop'
|
import { Tool } from '@/modules/5s/types/workshop'
|
||||||
|
|
||||||
export const tools: Tool[] = [
|
export const tools: Tool[] = [
|
||||||
{ name: 'Card Printer', id: 'card-printer', alias: '', cooldown: 5 },
|
{ name: 'Card Printer', id: 'card-printer', reference: '' },
|
||||||
{ name: 'Miniature Mold', id: 'mini-mold', alias: '', cooldown: 15 },
|
{ name: 'Miniature Mold', id: 'mini-mold', reference: '' },
|
||||||
{ name: 'Dice Engraver', id: 'dice-engraver', alias: '', cooldown: 10 },
|
{ name: 'Dice Engraver', id: 'dice-engraver', reference: '' },
|
||||||
{ name: 'Board Cutter', id: 'board-cutter', alias: '', cooldown: 8 },
|
{ name: 'Cutter', id: 'cutter', reference: '' },
|
||||||
{ name: 'Rulebook Designer', id: 'rulebook-dzn', alias: '', cooldown: 6 },
|
{ name: 'Rulebook Designer', id: 'rulebook-dzn', reference: '' },
|
||||||
{ name: 'Box Assembler', id: 'box-asm', alias: '', cooldown: 4 },
|
{ name: 'Box Assembler', id: 'box-asm', reference: '' },
|
||||||
{ name: 'Component Painter', id: 'painter', alias: '', cooldown: 12 },
|
{ name: 'Component Painter', id: 'painter', reference: '' },
|
||||||
{ name: 'Lamination Machine', id: 'laminator', alias: '', cooldown: 7 }
|
{ name: 'Lamination Machine', id: 'laminator', reference: '' },
|
||||||
|
// Additional realistic tools
|
||||||
|
{
|
||||||
|
name: 'Shrink Wrap Machine',
|
||||||
|
id: 'shrink-wrap',
|
||||||
|
reference: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Punch Board Cutter',
|
||||||
|
id: 'punch-cutter',
|
||||||
|
reference: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Sticker Applicator',
|
||||||
|
id: 'sticker-applicator',
|
||||||
|
reference: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Foil Stamping Press',
|
||||||
|
id: 'foil-stamp',
|
||||||
|
reference: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Scanner',
|
||||||
|
id: 'scanner',
|
||||||
|
reference: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Instruction Sheet Folder',
|
||||||
|
id: 'sheet-folder',
|
||||||
|
reference: ''
|
||||||
|
},
|
||||||
|
{ name: 'Plastic Bag Sealer', id: 'bag-sealer', reference: '' },
|
||||||
|
{ name: 'Barcode Printer', id: 'barcode-printer', reference: '' }
|
||||||
]
|
]
|
||||||
export type NonEmptyArray<T> = [T, ...T[]]
|
export type NonEmptyArray<T> = [T, ...T[]]
|
||||||
|
|
||||||
export const idToTools = (id: string): Tool => tools.find((t) => t.id === id)!
|
export const idToTools = (id: string): Tool => tools.find((t) => t.id === id)!
|
||||||
|
|
||||||
export const chooseTools = (...ides: string[]): NonEmptyArray<Tool> =>
|
export const chooseTools = (...ids: string[]): NonEmptyArray<Tool> =>
|
||||||
ides.map(idToTools) as NonEmptyArray<Tool>
|
ids.map(idToTools) as NonEmptyArray<Tool>
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ import { NonEmptyArray } from '@/modules/5s/types/tools'
|
|||||||
export type Tool = {
|
export type Tool = {
|
||||||
name: string
|
name: string
|
||||||
id: string
|
id: string
|
||||||
alias: string
|
reference: string
|
||||||
cooldown: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Task = {
|
export type Task = {
|
||||||
|
|||||||
@@ -18,7 +18,11 @@ export const toDuration = (startDate: Date, endDate: Date = new Date()) => {
|
|||||||
parts.push(`${minutes}m`)
|
parts.push(`${minutes}m`)
|
||||||
}
|
}
|
||||||
|
|
||||||
parts.push(`${seconds}s`)
|
parts.push(`${seconds}s`.padStart(3, '0'))
|
||||||
|
|
||||||
return parts.join(' ')
|
return parts.join(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const toSeconds = (start: Date, endDate: Date): number => {
|
||||||
|
return Math.floor((endDate.getTime() - start.getTime()) / 1000)
|
||||||
|
}
|
||||||
|
|||||||
10
src/utils.ts
10
src/utils.ts
@@ -21,6 +21,9 @@ export const getMean = (data: number[]) =>
|
|||||||
export const getRound = (data: number, total: number) =>
|
export const getRound = (data: number, total: number) =>
|
||||||
(Math.round(100 * (data / total)) / 100 || 0).toFixed(2)
|
(Math.round(100 * (data / total)) / 100 || 0).toFixed(2)
|
||||||
|
|
||||||
|
export const getNatural = (data: number, total: number) =>
|
||||||
|
(Math.round(100 * (data / total)) / 100 || 0).toFixed(0)
|
||||||
|
|
||||||
export const shuffleArray = <T>(array: T[]) => {
|
export const shuffleArray = <T>(array: T[]) => {
|
||||||
let currentIndex = array.length,
|
let currentIndex = array.length,
|
||||||
randomIndex
|
randomIndex
|
||||||
@@ -37,6 +40,13 @@ export const shuffleArray = <T>(array: T[]) => {
|
|||||||
return array
|
return array
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const accumulate = (array: string[]) => {
|
||||||
|
return array.reduce<Record<string, number>>((acc, toolId) => {
|
||||||
|
acc[toolId] = (acc[toolId] || 0) + 1
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
export const popNElement = <T>(array: T[], numberOfElements: number) => {
|
export const popNElement = <T>(array: T[], numberOfElements: number) => {
|
||||||
const poppedElements: T[] = []
|
const poppedElements: T[] = []
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user