Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions frontend/src/lib/components/admin/fournisseurCell.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<script lang="ts">
import type { Fournisseur } from '$lib/api';
import { fournisseurIterator } from '$lib/utils';

export let item: {
id: string;
fournisseur?: Fournisseur;
amount_per_bundle?: number;
ref_bundle?: string;
category_id: string;
};
export let editItem: (id: string, item: any, category_id: string) => void;

let expanded = false;

function getFournisseurName(fournisseur?: Fournisseur): string {
if (!fournisseur) return '-';
for (const [val, name] of fournisseurIterator) {
if (val === fournisseur) return name;
}
return String(fournisseur);
}
</script>

<div class="relative">
{#if !expanded}
<button
class="w-full text-left p-2 rounded-md border border-gray-200 dark:border-gray-600 hover:border-blue-400 dark:hover:border-blue-500 hover:bg-blue-50 dark:hover:bg-blue-950/30 transition-all group"
on:click={() => (expanded = true)}
>
<div class="flex items-center justify-between gap-2">
<div class="flex flex-col gap-1 text-xs flex-1 min-w-0">
<div class="flex items-center gap-2">
<span class="text-gray-500 dark:text-gray-400 font-medium">Fourn:</span>
<span class="font-semibold dark:text-gray-200 truncate">{getFournisseurName(item.fournisseur)}</span>
</div>
{#if item.amount_per_bundle || item.ref_bundle}
<div class="flex items-center gap-2 text-gray-600 dark:text-gray-400">
{#if item.amount_per_bundle}
<span>Bundle: {item.amount_per_bundle}</span>
{/if}
{#if item.ref_bundle}
<span>Ref: {item.ref_bundle}</span>
{/if}
</div>
{/if}
</div>
<iconify-icon icon="mdi:chevron-down" width="16" height="16" class="text-gray-400 group-hover:text-blue-500 transition-colors flex-shrink-0"></iconify-icon>
</div>
</button>
{:else}
<div class="border border-blue-500 dark:border-blue-400 rounded-md p-3 bg-blue-50/50 dark:bg-blue-950/20">
<div class="flex items-center justify-between mb-3">
<span class="text-xs font-semibold text-blue-700 dark:text-blue-300">Modifier Fournisseur</span>
<button
class="px-2 py-1 text-xs bg-white dark:bg-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 border border-gray-300 dark:border-gray-500 rounded flex items-center gap-1 transition-colors"
on:click={() => (expanded = false)}
>
<iconify-icon icon="mdi:check" width="14" height="14"></iconify-icon>
<span>Fermer</span>
</button>
</div>
<div class="space-y-2">
<div>
<label class="text-xs block font-medium dark:text-gray-300 mb-1">Fournisseur</label>
<select
class="w-full text-xs px-2 py-1.5 border border-gray-300 dark:border-gray-500 rounded bg-white dark:bg-gray-700 dark:text-white focus:border-blue-500 focus:ring-2 focus:ring-blue-200 dark:focus:ring-blue-800 outline-none transition-all"
value={item.fournisseur ?? ''}
on:change={(e) => {
// @ts-ignore
editItem(item.id, { fournisseur: e.target?.value || undefined }, item.category_id);
}}
>
<option value="">-</option>
{#each fournisseurIterator as [val, name]}
<option value={val}>{name}</option>
{/each}
</select>
</div>
<div>
<label class="text-xs block font-medium dark:text-gray-300 mb-1">Bundle/Quantité</label>
<input
type="number"
class="w-full text-xs px-2 py-1.5 border border-gray-300 dark:border-gray-500 rounded bg-white dark:bg-gray-700 dark:text-white focus:border-blue-500 focus:ring-2 focus:ring-blue-200 dark:focus:ring-blue-800 outline-none transition-all"
value={item.amount_per_bundle ?? ''}
placeholder="0"
on:input={(e) => {
// @ts-ignore
let val = e.target?.value;
editItem(item.id, { amount_per_bundle: val ? parseInt(val) : undefined }, item.category_id);
}}
/>
</div>
<div>
<label class="text-xs block font-medium dark:text-gray-300 mb-1">Code Référence</label>
<input
type="text"
class="w-full text-xs px-2 py-1.5 border border-gray-300 dark:border-gray-500 rounded bg-white dark:bg-gray-700 dark:text-white focus:border-blue-500 focus:ring-2 focus:ring-blue-200 dark:focus:ring-blue-800 outline-none transition-all"
value={item.ref_bundle ?? ''}
placeholder="-"
on:input={(e) => {
// @ts-ignore
editItem(item.id, { ref_bundle: e.target?.value || undefined }, item.category_id);
}}
/>
</div>
</div>
</div>
{/if}
</div>
106 changes: 106 additions & 0 deletions frontend/src/lib/components/admin/priceCell.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<script lang="ts">
import type { ItemPrices, AccountPriceRole } from '$lib/api';
import { formatPrice, parsePrice } from '$lib/utils';

export let item: { id: string; prices: ItemPrices; category_id: string };
export let editItem: (id: string, item: any, category_id: string) => void;

let expanded = false;
let rebounceTimeout: number | null = null;

const priceLabels: Record<string, string> = {
'coutant': 'Cout',
'externe': 'Ext',
'ceten': 'Ceten',
'staff_bar': 'Staff',
'privilegies': 'Priv',
'menu': 'Menu'
};

const priceRoles: AccountPriceRole[] = ['coutant', 'externe', 'ceten', 'staff_bar', 'privilegies', 'menu'];

function getCompactPrice(role: AccountPriceRole): string {
return `${priceLabels[role]}:${formatPrice(item.prices[role])}`;
}

function handlePriceChange(role: AccountPriceRole, e: Event) {
let prices = item.prices;
const target = e.target as HTMLInputElement;
prices[role] = parsePrice(target.value);
editItem(item.id, { prices: prices }, item.category_id);

if (rebounceTimeout) clearTimeout(rebounceTimeout);
rebounceTimeout = setTimeout(() => {
let elems = document.querySelectorAll(`[id^="price-${item.id}-"]`);
elems.forEach((elem) => {
(elem as HTMLInputElement).value = '';
});
}, 1000);
}
</script>

<div class="relative">
{#if !expanded}
<button
class="w-full text-left p-2 rounded-md border border-gray-200 dark:border-gray-600 hover:border-blue-400 dark:hover:border-blue-500 hover:bg-blue-50 dark:hover:bg-blue-950/30 transition-all group"
on:click={() => (expanded = true)}
>
<div class="flex items-center justify-between gap-2">
<div class="grid grid-cols-2 gap-x-3 gap-y-1 text-xs flex-1 min-w-0">
{#each priceRoles as role}
{@const label = priceLabels[role]}
{@const price = formatPrice(item.prices[role])}
<div class="flex items-center gap-1.5">
<span class="text-gray-500 dark:text-gray-400 font-medium">{label}:</span>
<span class="font-semibold dark:text-gray-200">{price}</span>
</div>
{/each}
</div>
<iconify-icon icon="mdi:chevron-down" width="16" height="16" class="text-gray-400 group-hover:text-blue-500 transition-colors flex-shrink-0"></iconify-icon>
</div>
</button>
{:else}
<div class="border border-blue-500 dark:border-blue-400 rounded-md p-3 bg-blue-50/50 dark:bg-blue-950/20">
<div class="flex items-center justify-between mb-3">
<span class="text-xs font-semibold text-blue-700 dark:text-blue-300">Modifier Prix</span>
<button
class="px-2 py-1 text-xs bg-white dark:bg-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 border border-gray-300 dark:border-gray-500 rounded flex items-center gap-1 transition-colors"
on:click={() => (expanded = false)}
>
<iconify-icon icon="mdi:check" width="14" height="14"></iconify-icon>
<span>Fermer</span>
</button>
</div>
<div class="space-y-2">
{#each priceRoles as role}
{@const price = item.prices[role]}
{@const label = role === 'coutant' ? 'Coutant' : role === 'externe' ? 'Externe' : role === 'ceten' ? 'Ceten' : role === 'staff_bar' ? 'Staff' : role === 'privilegies' ? 'Privil.' : 'Menu'}
<div class="flex items-center gap-2">
<label class="text-xs w-16 shrink-0 font-medium dark:text-gray-300">{label}:</label>
<div class="flex-1 relative">
<input
type="number"
id="price-{item.id}-{role}"
placeholder={formatPrice(price)}
class="w-full text-xs px-2 py-1.5 pr-6 border border-gray-300 dark:border-gray-500 rounded bg-white dark:bg-gray-700 dark:text-white focus:border-blue-500 focus:ring-2 focus:ring-blue-200 dark:focus:ring-blue-800 outline-none transition-all"
on:input={(e) => handlePriceChange(role, e)}
/>
<span class="absolute right-2 top-1/2 -translate-y-1/2 text-xs text-gray-400">€</span>
</div>
</div>
{/each}
</div>
</div>
{/if}
</div>

<style>
input[type='number'] {
-moz-appearance: textfield;
}
input[type='number']::-webkit-outer-spin-button,
input[type='number']::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
</style>
114 changes: 114 additions & 0 deletions frontend/src/lib/components/admin/stockCell.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<script lang="ts">
export let item: {
id: string;
amount_left: number;
buy_limit?: number;
optimal_amount: number;
category_id: string;
};
export let editItem: (id: string, item: any, category_id: string) => void;

let expanded = false;

function handleStockChange(e: Event) {
const target = e.target as HTMLInputElement;
let stock = parseInt(target.value);
editItem(item.id, { amount_left: stock }, item.category_id);
}

function handleLimitChange(e: Event) {
const target = e.target as HTMLInputElement;
if (target.value === '') {
editItem(item.id, { buy_limit: -1 }, item.category_id);
return;
}
let buy_limit = parseInt(target.value);
editItem(item.id, { buy_limit: buy_limit }, item.category_id);
}

function handleOptimalChange(e: Event) {
const target = e.target as HTMLInputElement;
let optimal_amount = parseInt(target.value);
editItem(item.id, { optimal_amount: optimal_amount }, item.category_id);
}
</script>

<div class="relative">
{#if !expanded}
<button
class="w-full text-left p-2 rounded-md border border-gray-200 dark:border-gray-600 hover:border-blue-400 dark:hover:border-blue-500 hover:bg-blue-50 dark:hover:bg-blue-950/30 transition-all group"
on:click={() => (expanded = true)}
>
<div class="flex items-center justify-between gap-2">
<div class="flex flex-col gap-1 text-xs flex-1 min-w-0">
<div class="flex items-center gap-2">
<span class="text-gray-500 dark:text-gray-400 font-medium w-14">Stock:</span>
<span class="font-semibold dark:text-gray-200">{item.amount_left}</span>
</div>
<div class="flex items-center gap-2">
<span class="text-gray-500 dark:text-gray-400 font-medium w-14">Limite:</span>
<span class="font-semibold dark:text-gray-200">{item.buy_limit ?? '-'}</span>
</div>
<div class="flex items-center gap-2">
<span class="text-gray-500 dark:text-gray-400 font-medium w-14">Optimal:</span>
<span class="font-semibold dark:text-gray-200">{item.optimal_amount}</span>
</div>
</div>
<iconify-icon icon="mdi:chevron-down" width="16" height="16" class="text-gray-400 group-hover:text-blue-500 transition-colors flex-shrink-0"></iconify-icon>
</div>
</button>
{:else}
<div class="border border-blue-500 dark:border-blue-400 rounded-md p-3 bg-blue-50/50 dark:bg-blue-950/20">
<div class="flex items-center justify-between mb-3">
<span class="text-xs font-semibold text-blue-700 dark:text-blue-300">Modifier Stocks</span>
<button
class="px-2 py-1 text-xs bg-white dark:bg-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 border border-gray-300 dark:border-gray-500 rounded flex items-center gap-1 transition-colors"
on:click={() => (expanded = false)}
>
<iconify-icon icon="mdi:check" width="14" height="14"></iconify-icon>
<span>Fermer</span>
</button>
</div>
<div class="space-y-2">
<div class="flex items-center gap-2">
<label class="text-xs w-16 shrink-0 font-medium dark:text-gray-300">Stock:</label>
<input
type="number"
value={item.amount_left}
class="flex-1 text-xs px-2 py-1.5 border border-gray-300 dark:border-gray-500 rounded bg-white dark:bg-gray-700 dark:text-white focus:border-blue-500 focus:ring-2 focus:ring-blue-200 dark:focus:ring-blue-800 outline-none transition-all"
on:input={handleStockChange}
/>
</div>
<div class="flex items-center gap-2">
<label class="text-xs w-16 shrink-0 font-medium dark:text-gray-300">Limite:</label>
<input
type="number"
value={item.buy_limit}
class="flex-1 text-xs px-2 py-1.5 border border-gray-300 dark:border-gray-500 rounded bg-white dark:bg-gray-700 dark:text-white focus:border-blue-500 focus:ring-2 focus:ring-blue-200 dark:focus:ring-blue-800 outline-none transition-all"
on:input={handleLimitChange}
/>
</div>
<div class="flex items-center gap-2">
<label class="text-xs w-16 shrink-0 font-medium dark:text-gray-300">Optimal:</label>
<input
type="number"
value={item.optimal_amount}
class="flex-1 text-xs px-2 py-1.5 border border-gray-300 dark:border-gray-500 rounded bg-white dark:bg-gray-700 dark:text-white focus:border-blue-500 focus:ring-2 focus:ring-blue-200 dark:focus:ring-blue-800 outline-none transition-all"
on:input={handleOptimalChange}
/>
</div>
</div>
</div>
{/if}
</div>

<style>
input[type='number'] {
-moz-appearance: textfield;
}
input[type='number']::-webkit-outer-spin-button,
input[type='number']::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
</style>
5 changes: 0 additions & 5 deletions frontend/src/lib/components/panel/modules.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,6 @@
color: 'bg-green-600',
link: '/panel/products/incoherants'
});
modules.push({
name: 'Fournisseur',
color: 'bg-green-600',
link: '/panel/products/fournisseur'
});
modules.push({
name: 'Course',
color: 'bg-green-600',
Expand Down
Loading