Compare commits

..

10 Commits

Author SHA1 Message Date
Julien Calixte
07a4953504 feat: change topics and fix image in Firefox 2026-02-21 21:52:02 +01:00
Julien Calixte
aa9e068adc feat: wip 2026-02-21 21:46:28 +01:00
Julien Calixte
08dd330fc3 fix: remove warning 2026-02-02 16:10:29 +01:00
Julien Calixte
d0e5a07d3b deps: upgrade deps 2026-02-02 15:25:17 +01:00
Julien Calixte
4ed8516259 design: change primary color for change 2026-01-24 23:20:37 +01:00
Julien Calixte
efc6655afd feat: init cash flow 2026-01-03 20:49:10 +01:00
Julien Calixte
df83fb5a29 design: lower title size 2026-01-03 20:48:58 +01:00
Julien Calixte
95a17c9820 article: add some planification paraphraph 2026-01-03 20:24:04 +01:00
Julien Calixte
70581f00d9 feat: add better colors 2026-01-03 20:22:19 +01:00
Julien Calixte
1accacdac1 refacto: remove planning state 2026-01-03 11:23:51 +01:00
9 changed files with 124 additions and 54 deletions

View File

@@ -1,7 +1,7 @@
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif&family=Cutive+Mono&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Noto+Serif&family=Cutive+Mono&display=swap');
:root { :root {
--primary-color: #002992; --primary-color: #30394a;
--primary-color-no-focus: #abbbdf; --primary-color-no-focus: #abbbdf;
--color: white; --color: white;
--font-size: 28px; --font-size: 28px;
@@ -19,7 +19,7 @@
} }
body { body {
font-size: 28px; font-size: 22px;
font-family: 'Noto Serif', serif; font-family: 'Noto Serif', serif;
margin: 0; margin: 0;
} }

View File

@@ -64,6 +64,10 @@ li {
font-size: clamp(var(--min-font-size, 11px), 2.1vw, var(--font-size, 28px)); font-size: clamp(var(--min-font-size, 11px), 2.1vw, var(--font-size, 28px));
} }
.customer-satisfaction h2 {
display: block;
}
.customer-satisfaction h2, .customer-satisfaction h2,
.customer-satisfaction p { .customer-satisfaction p {
margin: 0; margin: 0;

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
@@ -24,8 +24,8 @@
<section class="customer-satisfaction focusable"> <section class="customer-satisfaction focusable">
<h2 class="customer-satisfaction-title">Customer Satisfaction</h2> <h2 class="customer-satisfaction-title">Customer Satisfaction</h2>
<p class="value-analysis-value-engineering"> <p class="value-analysis-value-engineering">
<span class="use-value">Use</span> Value analysis / <span class="use-value">Use</span> Value Analysis /
<span class="use-value">Use</span> Value engineering <span class="use-value">Use</span> Value Engineering
</p> </p>
</section> </section>
</div> </div>
@@ -40,12 +40,12 @@
</section> </section>
<section class="pilars"> <section class="pilars">
<section class="just-in-time-pilar"> <section class="just-in-time-pilar">
<h3 class="just-in-time focusable">Just-in-time</h3> <h3 class="just-in-time focusable">Just-in-Time</h3>
<ul> <ul>
<li class="takt-time takt focusable">Takt time</li> <li class="takt-time takt focusable">Takt Time</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">Pull system</a> <a href="/pull-system">Pull System</a>
</li> </li>
</ul> </ul>
</section> </section>
@@ -99,8 +99,8 @@
<h3 class="jidoka focusable">Jidoka</h3> <h3 class="jidoka focusable">Jidoka</h3>
<ul> <ul>
<li class="andon focusable">Andon</li> <li class="andon focusable">Andon</li>
<li class="poka-yoke focusable">Poka yoke</li> <li class="poka-yoke focusable">Poka Yoke</li>
<li class="human-machine focusable">Human / machine</li> <li class="human-machine focusable">Human / Machine</li>
</ul> </ul>
</section> </section>
</section> </section>
@@ -111,7 +111,9 @@
<!-- <a href="/heijunka">Heijunka</a> --> <!-- <a href="/heijunka">Heijunka</a> -->
Heijunka Heijunka
</li> </li>
<li class="standards focusable">Standards</li> <li class="standards standardized-work focusable">
Standardized Work
</li>
<li class="kaizen focusable">Kaizen</li> <li class="kaizen focusable">Kaizen</li>
</ul> </ul>
</section> </section>
@@ -121,7 +123,7 @@
<!-- <a href="/5s">5S</a> --> <!-- <a href="/5s">5S</a> -->
5S 5S
</li> </li>
<li class="problem-solving focusable">Problem solving</li> <li class="problem-solving focusable">Problem Solving</li>
<li class="tpm focusable">TPM</li> <li class="tpm focusable">TPM</li>
</ul> </ul>
</section> </section>

View File

@@ -13,7 +13,7 @@ const focusList = document.querySelector('#focus-list')
if (focusList) { if (focusList) {
focusables.forEach((focusable, index) => { focusables.forEach((focusable, index) => {
const a = document.createElement('a') const a = document.createElement('a')
a.textContent = [...focusable.classList] a.textContent = Array.from(focusable.classList)
.filter((c) => c !== 'focusable') .filter((c) => c !== 'focusable')
.join(' ') .join(' ')
a.href = `?focus=${a.textContent}` a.href = `?focus=${a.textContent}`
@@ -103,6 +103,7 @@ const screenshotHouseButton = document.querySelector('#screenshot-house')
if (screenshotHouseButton) { if (screenshotHouseButton) {
screenshotHouseButton.addEventListener('click', async () => { screenshotHouseButton.addEventListener('click', async () => {
const house = document.querySelector('#thinking-people-system') const house = document.querySelector('#thinking-people-system')
if (!house) { if (!house) {
return return
} }

View File

@@ -22,7 +22,7 @@
"comlink": "^4.4.2", "comlink": "^4.4.2",
"daisyui": "^5.0.50", "daisyui": "^5.0.50",
"hex-color-regex": "^1.1.0", "hex-color-regex": "^1.1.0",
"modern-screenshot": "^4.5.5", "modern-screenshot": "^4.6.8",
"pinia": "^2.3.1", "pinia": "^2.3.1",
"random-js": "^2.1.0", "random-js": "^2.1.0",
"tailwindcss": "^4.1.11", "tailwindcss": "^4.1.11",

View File

@@ -19,7 +19,7 @@ import OrderItem from '@/modules/heijkunka/assets/OrderItem.vue'
const days = Array.from({ length: NUMBER_OF_DAYS }, (_, i) => i + 1) const days = Array.from({ length: NUMBER_OF_DAYS }, (_, i) => i + 1)
const hours = Array.from({ length: NUMBER_OF_HOURS_PER_DAY }, (_, i) => i + 1) const hours = Array.from({ length: NUMBER_OF_HOURS_PER_DAY }, (_, i) => i + 1)
const orders = ref( const planning = ref(
Array.from( Array.from(
{ length: days.length * hours.length }, { length: days.length * hours.length },
(): ProductType => pickRandomElement(['shirt', 'jeans', 'shoes', 'hat']) (): ProductType => pickRandomElement(['shirt', 'jeans', 'shoes', 'hat'])
@@ -56,7 +56,7 @@ const levelingPlanning: ProductType[] = [
'jeans' 'jeans'
] ]
const orderIndex = (dayIndex: number, hourIndex: number) => { const planningIndex = (dayIndex: number, hourIndex: number) => {
return dayIndex * hours.length + hourIndex return dayIndex * hours.length + hourIndex
} }
@@ -103,6 +103,11 @@ const createdAt = new Date('2026-01-01').toLocaleDateString(undefined, {
per day to meet the 12 orders every 3 days. At the end, you produce 1 per day to meet the 12 orders every 3 days. At the end, you produce 1
product every hour. product every hour.
</p> </p>
<p>
The mere reality is that we don't really know the exact orders we'll have.
This is presisely the whole point of plannification, it is a bet into the
future. The play is to know how to bet.
</p>
<section class="factory"> <section class="factory">
<h2>Factory</h2> <h2>Factory</h2>
@@ -110,10 +115,11 @@ const createdAt = new Date('2026-01-01').toLocaleDateString(undefined, {
<thead> <thead>
<tr> <tr>
<th scope="col"></th> <th scope="col"></th>
<th scope="col">hour 1</th> <th scope="col">8:00</th>
<th scope="col">hour 2</th> <th scope="col">9:00</th>
<th scope="col">hour 3</th> <th scope="col">10:00</th>
<th scope="col">hour 4</th> <th scope="col">11:00</th>
<th>dock</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -121,7 +127,7 @@ const createdAt = new Date('2026-01-01').toLocaleDateString(undefined, {
<th scope="row">day {{ day }}</th> <th scope="row">day {{ day }}</th>
<td v-for="(hour, hourIndex) in hours"> <td v-for="(hour, hourIndex) in hours">
<select <select
v-model="orders[orderIndex(dayIndex, hourIndex)]" v-model="planning[planningIndex(dayIndex, hourIndex)]"
:name="`day-${day}-hour-${hour}`" :name="`day-${day}-hour-${hour}`"
:id="`day-${day}-hour-${hour}`" :id="`day-${day}-hour-${hour}`"
> >
@@ -131,13 +137,34 @@ const createdAt = new Date('2026-01-01').toLocaleDateString(undefined, {
<option value="hat">Hat</option> <option value="hat">Hat</option>
</select> </select>
</td> </td>
<td>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="icon icon-tabler icons-tabler-outline icon-tabler-truck"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M5 17a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
<path d="M15 17a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />
<path
d="M5 17h-2v-11a1 1 0 0 1 1 -1h9v12m-4 0h6m4 0h2v-6h-8m0 -5h5l3 5"
/>
</svg>
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</section> </section>
<section class="commands"> <section class="commands">
<button class="button-outline" @click="heijunkaStore.newHour()"> <button class="button-outline" @click="heijunkaStore.newHour(planning)">
next hour next hour
<!-- <!--
<svg <svg
@@ -161,7 +188,7 @@ const createdAt = new Date('2026-01-01').toLocaleDateString(undefined, {
reset reset
</button> </button>
<button class="button-outline" @click="orders = [...levelingPlanning]"> <button class="button-outline" @click="planning = [...levelingPlanning]">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="24" width="24"
@@ -184,7 +211,7 @@ const createdAt = new Date('2026-01-01').toLocaleDateString(undefined, {
</svg> </svg>
levelling levelling
</button> </button>
<button class="button-outline" @click="orders = [...batchPlanning]"> <button class="button-outline" @click="planning = [...batchPlanning]">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="24" width="24"
@@ -200,14 +227,18 @@ const createdAt = new Date('2026-01-01').toLocaleDateString(undefined, {
</svg> </svg>
batch batch
</button> </button>
<button class="button-outline" @click="heijunkaStore.simulateMonth()"> <button
class="button-outline"
@click="heijunkaStore.simulateMonth(planning)"
>
simulate a month simulate a month
</button> </button>
</section> </section>
<div> <div>
<span v-if="heijunkaStore.meta.currentHour > 0"> <span v-if="heijunkaStore.meta.currentHour > 0">
day: {{ heijunkaStore.currentDay }} | current hour: day: {{ heijunkaStore.currentDay }} | current hour:
{{ heijunkaStore.meta.currentHour }} hours {{ heijunkaStore.meta.currentHour }} hours | cash flow:
{{ heijunkaStore.cashFlow }}
</span> </span>
</div> </div>
@@ -217,8 +248,6 @@ const createdAt = new Date('2026-01-01').toLocaleDateString(undefined, {
Orders made: {{ heijunkaStore.orders.length }} Orders made: {{ heijunkaStore.orders.length }}
</section> </section>
<HeijunkaStat />
<section class="shop"> <section class="shop">
<div class="inventory"> <div class="inventory">
<h2>Inventory</h2> <h2>Inventory</h2>
@@ -269,9 +298,12 @@ const createdAt = new Date('2026-01-01').toLocaleDateString(undefined, {
<OrderItem /> <OrderItem />
<span class="numeric"> <ShirtItem v-show="order.product === 'shirt'" />
{{ order.product }} | {{ order.leadTime }} <JeanItem v-show="order.product === 'jeans'" />
</span> <ShoeItem v-show="order.product === 'shoes'" />
<HatItem v-show="order.product === 'hat'" />
<span class="numeric">{{ order.leadTime }}</span>
</li> </li>
</ol> </ol>
</div> </div>
@@ -283,6 +315,8 @@ const createdAt = new Date('2026-01-01').toLocaleDateString(undefined, {
Orders made: {{ heijunkaStore.orders.length }} Orders made: {{ heijunkaStore.orders.length }}
</section> </section>
<HeijunkaStat />
<p> <p>
The longer the lead time is, the longer it takes to have return on The longer the lead time is, the longer it takes to have return on
investment. You already paid for the raw material, the workforce, the investment. You already paid for the raw material, the workforce, the
@@ -319,7 +353,11 @@ const createdAt = new Date('2026-01-01').toLocaleDateString(undefined, {
what you can make per day. what you can make per day.
</p> </p>
<h2>Heijunka is fun</h2> <h2>Heijunka is fun</h2>
<p>There's no ///</p> <p>
For craftspersonns, there's no such thing repeating over and over again
the making of the same product - even if you love doing it - work needs
diversity. This is what the heijunka adds by doing a bit of everything.
</p>
</article> </article>
</template> </template>
@@ -347,17 +385,36 @@ li {
align-items: center; align-items: center;
} }
.factory {
border: 2px solid var(--primary-color);
padding: 0 1rem;
margin: 1rem 0;
}
.shop { .shop {
display: flex; display: flex;
border: 2px solid var(--primary-color); border: 2px solid var(--primary-color);
border-radius: 1rem; border-radius: 1rem;
padding: 0 0.5rem; padding: 0 0.5rem;
width: 100%;
h2 {
text-align: center;
}
& > div { & > div {
flex: 1; flex: 1;
} }
} }
.orders {
ol {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
}
button { button {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@@ -22,10 +22,10 @@ const renderChart = () => {
const config = { const config = {
title: `Orders made`, title: `Orders made`,
xLabel: 'Products', xLabel: 'Products (ordered | made)',
yLabel: '# of orders', yLabel: '# of orders',
data: { data: {
labels: products.map((p) => [`${p} ordered`, `${p} made`]).flat(), labels: products.map((p) => [`${p} o`, `${p}`]).flat(),
datasets: [ datasets: [
{ {
data: products data: products
@@ -39,7 +39,18 @@ const renderChart = () => {
}, },
options: { options: {
showLegend: true, showLegend: true,
fontFamily: 'Noto Serif' fontFamily: 'Noto Serif',
dataColors: [
'#55efc4',
'#00b894',
'#81ecec',
'#00cec9',
'#74b9ff',
'#0984e3',
'#a29bfe',
'#6c5ce7'
],
strokeColor: 'var(--primary-color)'
} }
} }
new chartXkcd.Bar(svgElement.value, config) new chartXkcd.Bar(svgElement.value, config)

View File

@@ -1,2 +1,3 @@
export const NUMBER_OF_DAYS = 3 export const NUMBER_OF_DAYS = 3
export const NUMBER_OF_HOURS_PER_DAY = 4 export const NUMBER_OF_HOURS_PER_DAY = 4
export const INITIAL_CASH_FLOW = 20

View File

@@ -1,4 +1,5 @@
import { import {
INITIAL_CASH_FLOW,
NUMBER_OF_DAYS, NUMBER_OF_DAYS,
NUMBER_OF_HOURS_PER_DAY NUMBER_OF_HOURS_PER_DAY
} from '@/modules/heijkunka/heijunka-config' } from '@/modules/heijkunka/heijunka-config'
@@ -20,10 +21,8 @@ type Inventory = {
} }
type HeijunkaState = { type HeijunkaState = {
money: number
inventory: Inventory inventory: Inventory
orders: Order[] orders: Order[]
planning: ProductType[]
meta: { meta: {
currentHour: number currentHour: number
} }
@@ -53,21 +52,14 @@ const initialInventory: Inventory = {
export const useHeijunkaStore = defineStore('heijunka', { export const useHeijunkaStore = defineStore('heijunka', {
state: (): HeijunkaState => ({ state: (): HeijunkaState => ({
money: 100,
inventory: { ...initialInventory }, inventory: { ...initialInventory },
orders: [], orders: [],
planning: [],
meta: { meta: {
currentHour: 0 currentHour: 0
} }
}), }),
actions: { actions: {
newHour() { newHour(planning: ProductType[]) {
// End of the production
if (this.gameEnded) {
return
}
this.meta.currentHour++ this.meta.currentHour++
// Add to inventory every day // Add to inventory every day
@@ -75,16 +67,16 @@ export const useHeijunkaStore = defineStore('heijunka', {
this.inventory = { this.inventory = {
shirt: shirt:
this.inventory.shirt + this.inventory.shirt +
getInventoryByProduct('shirt', this.planning, this.currentDay), getInventoryByProduct('shirt', planning, this.currentDay),
jeans: jeans:
this.inventory.jeans + this.inventory.jeans +
getInventoryByProduct('jeans', this.planning, this.currentDay), getInventoryByProduct('jeans', planning, this.currentDay),
shoes: shoes:
this.inventory.shoes + this.inventory.shoes +
getInventoryByProduct('shoes', this.planning, this.currentDay), getInventoryByProduct('shoes', planning, this.currentDay),
hat: hat:
this.inventory.hat + this.inventory.hat +
getInventoryByProduct('hat', this.planning, this.currentDay) getInventoryByProduct('hat', planning, this.currentDay)
} }
} }
@@ -136,19 +128,22 @@ export const useHeijunkaStore = defineStore('heijunka', {
}, },
reset() { reset() {
this.meta.currentHour = 0 this.meta.currentHour = 0
this.planning = []
this.orders = [] this.orders = []
this.inventory = { ...initialInventory } this.inventory = { ...initialInventory }
}, },
simulateMonth() { simulateMonth(planning: ProductType[]) {
for (let index = 0; index < 80; index++) { for (let index = 0; index < 80; index++) {
this.newHour() this.newHour(planning)
} }
} }
}, },
getters: { getters: {
currentDay: (state) => currentDay: (state) =>
Math.ceil(state.meta.currentHour / NUMBER_OF_HOURS_PER_DAY), Math.ceil(state.meta.currentHour / NUMBER_OF_HOURS_PER_DAY),
cashFlow: (state) =>
INITIAL_CASH_FLOW -
state.meta.currentHour * 1 +
state.orders.filter((o) => o.status === 'received').length * 2,
remainingInventory: (state): Inventory => ({ remainingInventory: (state): Inventory => ({
shirt: Math.max( shirt: Math.max(
state.inventory.shirt - state.inventory.shirt -
@@ -171,7 +166,6 @@ export const useHeijunkaStore = defineStore('heijunka', {
0 0
) )
}), }),
gameEnded: () => false,
// state.meta.currentHour >= NUMBER_OF_DAYS * NUMBER_OF_HOURS_PER_DAY, // state.meta.currentHour >= NUMBER_OF_DAYS * NUMBER_OF_HOURS_PER_DAY,
meanLeadTime: (state) => getMean(state.orders.map((o) => o.leadTime)) meanLeadTime: (state) => getMean(state.orders.map((o) => o.leadTime))
} }