Files
librodrome/app/components/gratewizard/GwCRA.vue
Yvv 554602ad52 Pin Bloc 0 and Arrivant juste at top of relations list, sort others alphabetically
- Rename "Newbie" to "Arrivant juste"
- Base friends (Bloc 0, Arrivant juste) always stay at the top
- User-added relations are sorted below them

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 16:05:43 +01:00

195 lines
7.5 KiB
Vue

<template>
<div class="flex flex-col gap-5 pt-4">
<div class="card-surface">
<div class="flex flex-col items-center gap-3">
<p class="gw-metric">Coefficient relatif &agrave; l'anciennet&eacute;</p>
<!-- My seniority -->
<div class="flex flex-col items-center gap-1 w-full max-w-80">
<label class="gw-text text-sm">Mon anciennet&eacute;</label>
<input
v-model="myDate"
type="date"
:min="Block0Date"
:max="todayDate"
class="gw-input w-full"
/>
<p :class="['gw-text text-center text-sm', { invisible: !myDate }]">
{{ getDays(myDate) || 0 }} DUs cr&eacute;&eacute;s
</p>
</div>
<!-- Role -->
<div class="flex items-center justify-center gap-2 mt-2">
<span class="gw-text text-xs text-right">offre<br />le produit ou service</span>
<label class="gw-toggle shrink-0">
<input v-model="isSeller" type="checkbox" />
<span class="gw-toggle-slider" />
</label>
<span class="gw-text text-xs">re&ccedil;oit<br />le produit ou service</span>
</div>
<!-- Other party name + add button -->
<div class="flex items-center gap-4 w-full max-w-80">
<div class="flex-1">
<label class="gw-text text-sm">Nom {{ !isSeller ? 'du receveur' : "de l'offreur" }}</label>
<input
v-model="otherName"
type="text"
maxlength="25"
class="gw-input w-full"
/>
</div>
<button
v-if="!isBaseFriend"
class="gw-icon-btn self-end mb-0.5"
:disabled="!otherName || !otherDate || isFriend"
:title="friends.some((f) => f.name === otherName) ? 'Mettre \u00e0 jour' : 'Ajouter relation'"
@click="addFriend"
>
<svg v-if="friends.some((f) => f.name === otherName)" class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 2v6h-6M3 12a9 9 0 0 1 15-6.7L21 8M3 22v-6h6M21 12a9 9 0 0 1-15 6.7L3 16" /></svg>
<svg v-else class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19" /><line x1="5" y1="12" x2="19" y2="12" /></svg>
</button>
</div>
<!-- Other party date -->
<div class="flex flex-col items-center gap-1 w-full max-w-80">
<label class="gw-text text-sm">
Anciennet&eacute; {{ otherName ? 'de ' + otherName : !isSeller ? 'du receveur' : "de l'offreur" }}
</label>
<input
v-model="otherDate"
type="date"
:min="Block0Date"
:max="todayDate"
class="gw-input w-full max-w-64"
/>
<p :class="['gw-text text-center text-sm', { invisible: !otherDate }]">
{{ getDays(otherDate) || 0 }} DUs cr&eacute;&eacute;s
</p>
</div>
<!-- Price -->
<div class="flex items-center gap-3 justify-center">
<label class="gw-text text-sm whitespace-nowrap">&Eacute;valuation de r&eacute;f.</label>
<div class="relative">
<input
:value="price"
type="number"
min="0"
:step="Math.ceil(Number(price) / 10 / 2) || 1"
placeholder="0.00"
class="gw-input w-40 pr-14"
@input="price = Math.min(Number(($event.target as HTMLInputElement).value), 9999).toString()"
/>
<select
v-model="currency"
class="absolute right-2 top-1/2 -translate-y-1/2 bg-transparent text-white/40 text-sm cursor-pointer border-0 outline-none"
@change="price = '1'"
>
<option value="DU" class="bg-surface-100">DU</option>
<option value="G1" class="bg-surface-100">&#x11E;1</option>
</select>
</div>
</div>
<!-- Discount -->
<div class="flex items-center gap-3 justify-center">
<label class="gw-text text-sm whitespace-nowrap">R&eacute;duction newbie</label>
<div class="relative">
<input
:value="discount"
type="number"
min="0"
:step="Math.ceil(Number(discount) / 10 / 2) || 1"
placeholder="0"
class="gw-input w-40 pr-8"
@input="discount = Math.min(Number(($event.target as HTMLInputElement).value), 99).toString()"
/>
<span class="absolute right-2 top-1/2 -translate-y-1/2 text-white/40 text-sm pointer-events-none">%</span>
</div>
</div>
<!-- Corrected price -->
<p class="gw-title mt-4">
&Eacute;valuation corrig&eacute;e : {{ fr(getFinalPrice(isSeller ? myDate : otherDate)) }} {{ currencyDisplay }}
</p>
</div>
</div>
<!-- Relations table -->
<GratewizardGwRelations
:items="tableItems"
:base-friends="baseFriends"
result-label="ÉVAL."
@select="(f) => { otherName = f.name; otherDate = f.date; }"
@remove="removeFriend"
/>
</div>
</template>
<script setup lang="ts">
import { Block0Date, getDays, getRatio, dateToString, fr, type Friend, type TableFriend } from '~/utils/gratewizard';
const todayDate = dateToString(new Date());
const baseFriends: Friend[] = [
{ name: 'Bloc 0', date: Block0Date },
{ name: 'Arrivant juste', date: todayDate },
];
const price = useLocalStorage('price', '1');
const discount = useLocalStorage('discount', '0');
const myDate = useLocalStorage<string | undefined>('myDate', undefined);
const isSeller = useLocalStorage('isSeller', true);
const currency = useLocalStorage('currency', 'DU');
const otherName = useLocalStorage('otherName', '');
const otherDate = useLocalStorage<string | undefined>('otherDate', undefined);
const friends = useLocalStorage<Friend[]>('friends', []);
const currencyDisplay = computed(() => currency.value === 'G1' ? '\u011e1' : 'DU');
function getFinalPrice(date: string | undefined): number {
if ((!date && !myDate.value) || (!date && !otherDate.value)) return Number(price.value);
const ratio = date
? myDate.value && (!isSeller.value || !otherDate.value)
? getRatio(date, myDate.value)
: getRatio(date, otherDate.value)
: 1;
const d = Number(discount.value) / 100;
const p = Number(price.value);
return (1 - d) * p + d * p * ratio;
}
function addFriend() {
if (!otherDate.value || !otherName.value) return;
if (friends.value.some((f) => f.name === otherName.value)) {
friends.value = friends.value.map((f) =>
f.name === otherName.value ? { ...f, date: otherDate.value! } : f
);
} else {
friends.value = [...friends.value, { name: otherName.value, date: otherDate.value }];
}
}
function removeFriend(name: string) {
friends.value = friends.value.filter((f) => f.name !== name);
}
const isBaseFriend = computed(() =>
baseFriends.some((f) => f.name === otherName.value && f.date === otherDate.value)
);
const isFriend = computed(() =>
friends.value.some((f) => f.name === otherName.value && f.date === otherDate.value)
);
const tableItems = computed<TableFriend[]>(() => {
return baseFriends.concat(friends.value).map((friend) => ({
...friend,
displayName: friend.name.substring(0, 10),
displayDate: new Date(friend.date).toLocaleDateString('fr-FR', { dateStyle: 'short' }),
result: fr(getFinalPrice(friend.date)),
du: getDays(friend.date),
}));
});
</script>