feat(F-080): completed feature
This commit is contained in:
@@ -22,20 +22,17 @@ function calcMarginBruto(grossCents: number, costCents: number): number {
|
||||
return Math.round(((grossCents - costCents) / grossCents) * 100);
|
||||
}
|
||||
|
||||
interface PricingSectionProps {
|
||||
productId: string;
|
||||
}
|
||||
|
||||
export function PricingSection({ productId }: { productId: string }) {
|
||||
const [variants, setVariants] = useState<ProductVariant[]>([]);
|
||||
const [loadingVariants, setLoadingVariants] = useState(true);
|
||||
const [loadingPrices, setLoadingPrices] = useState(true);
|
||||
const [prices, setPrices] = useState<Record<string, VariantPrice>>({});
|
||||
const [, setLoadingPrices] = useState(true);
|
||||
const [, setPrices] = useState<Record<string, VariantPrice>>({});
|
||||
const [saving, setSaving] = useState<string | null>(null);
|
||||
const [msg, setMsg] = useState<Record<string, string>>({});
|
||||
|
||||
// Edit state per variant (valores en EUROS como string, p.ej. "12.00")
|
||||
const [net, setNet] = useState<Record<string, string>>({});
|
||||
const [gross, setGross] = useState<Record<string, string>>({});
|
||||
const [offer, setOffer] = useState<Record<string, string>>({});
|
||||
const [cost, setCost] = useState<Record<string, string>>({});
|
||||
const [activeTaxRates, setActiveTaxRates] = useState<TaxRate[]>([]);
|
||||
@@ -57,14 +54,19 @@ export function PricingSection({ productId }: { productId: string }) {
|
||||
for (const v of variants) {
|
||||
pricingApi.getVariantPrice(v.id)
|
||||
.then((p) => {
|
||||
const initialVr = p.vatRate;
|
||||
const initialRateP = activeTaxRates.find(r => r.appliesTo === initialVr)?.ratePercent ?? 21;
|
||||
const initialGrossCents = Math.round(p.netUnitAmountCents * (1 + initialRateP / 100));
|
||||
setPrices(prev => ({ ...prev, [v.id]: p }));
|
||||
setNet(prev => ({ ...prev, [v.id]: centsToEur(p.netUnitAmountCents) }));
|
||||
setGross(prev => ({ ...prev, [v.id]: centsToEur(initialGrossCents) }));
|
||||
setOffer(prev => ({ ...prev, [v.id]: p.offerCents !== null ? centsToEur(p.offerCents) : '' }));
|
||||
setCost(prev => ({ ...prev, [v.id]: p.costCents !== null ? centsToEur(p.costCents) : '' }));
|
||||
setVatRate(prev => ({ ...prev, [v.id]: p.vatRate }));
|
||||
setVatRate(prev => ({ ...prev, [v.id]: initialVr }));
|
||||
})
|
||||
.catch(() => {
|
||||
setNet(prev => ({ ...prev, [v.id]: '0.00' }));
|
||||
setGross(prev => ({ ...prev, [v.id]: '0.00' }));
|
||||
setOffer(prev => ({ ...prev, [v.id]: '' }));
|
||||
setCost(prev => ({ ...prev, [v.id]: '' }));
|
||||
setVatRate(prev => ({ ...prev, [v.id]: (activeTaxRates[0]?.appliesTo ?? 'general') as 'general' | 'reduced' | 'super-reduced' }));
|
||||
@@ -84,7 +86,10 @@ export function PricingSection({ productId }: { productId: string }) {
|
||||
}, []);
|
||||
|
||||
const savePrice = async (variantId: string) => {
|
||||
const netCents = eurToCents(net[variantId] ?? '0');
|
||||
const grossCents = eurToCents(gross[variantId] ?? '0');
|
||||
const vr = vatRate[variantId] ?? 'general';
|
||||
const rateP = activeTaxRates.find(r => r.appliesTo === vr)?.ratePercent ?? 21;
|
||||
const netCents = Math.round(grossCents / (1 + rateP / 100));
|
||||
const offerCentsVal = offer[variantId] ? eurToCents(offer[variantId]) : null;
|
||||
const costCentsVal = cost[variantId] ? eurToCents(cost[variantId]) : null;
|
||||
if (netCents < 0) return;
|
||||
@@ -93,7 +98,7 @@ export function PricingSection({ productId }: { productId: string }) {
|
||||
setSaving(variantId);
|
||||
setMsg(prev => ({ ...prev, [variantId]: '' }));
|
||||
try {
|
||||
const updated = await pricingApi.setVariantPrice(variantId, netCents, vatRate[variantId], offerCentsVal, costCentsVal);
|
||||
const updated = await pricingApi.setVariantPrice(variantId, netCents, vr, offerCentsVal, costCentsVal);
|
||||
setPrices(prev => ({ ...prev, [variantId]: updated }));
|
||||
setMsg(prev => ({ ...prev, [variantId]: '✓' }));
|
||||
setTimeout(() => setMsg(prev => ({ ...prev, [variantId]: '' })), 3000);
|
||||
@@ -137,8 +142,7 @@ export function PricingSection({ productId }: { productId: string }) {
|
||||
const vr = vatRate[v.id] ?? 'general';
|
||||
const activeRate = activeTaxRates.find(r => r.appliesTo === vr);
|
||||
const ratePercent = activeRate?.ratePercent ?? 21;
|
||||
const grossCents = Math.round(netCents * (1 + ratePercent / 100));
|
||||
const grossEur = centsToEur(grossCents);
|
||||
const grossCents = eurToCents(gross[v.id] ?? '0');
|
||||
const marginBruto = calcMarginBruto(grossCents, costCents);
|
||||
const editing = saving === v.id;
|
||||
|
||||
@@ -151,7 +155,7 @@ export function PricingSection({ productId }: { productId: string }) {
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-gray-400 text-xs">€</span>
|
||||
<input
|
||||
type="number" min={0} step="0.01" inputMode="decimal"
|
||||
type="text" inputMode="decimal"
|
||||
value={cost[v.id] ?? ''}
|
||||
disabled={editing}
|
||||
onChange={e => setCost(prev => ({ ...prev, [v.id]: e.target.value }))}
|
||||
@@ -166,11 +170,13 @@ export function PricingSection({ productId }: { productId: string }) {
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-gray-400 text-xs">€</span>
|
||||
<input
|
||||
type="number" min={0} step="0.01" inputMode="decimal"
|
||||
value={grossEur}
|
||||
type="text" inputMode="decimal"
|
||||
value={gross[v.id] ?? ''}
|
||||
disabled={editing}
|
||||
onChange={e => {
|
||||
const grossInputCents = eurToCents(e.target.value);
|
||||
const raw = e.target.value;
|
||||
setGross(prev => ({ ...prev, [v.id]: raw }));
|
||||
const grossInputCents = eurToCents(raw);
|
||||
const newNetCents = Math.round(grossInputCents / (1 + ratePercent / 100));
|
||||
setNet(prev => ({ ...prev, [v.id]: centsToEur(newNetCents) }));
|
||||
}}
|
||||
@@ -184,7 +190,7 @@ export function PricingSection({ productId }: { productId: string }) {
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-gray-400 text-xs">€</span>
|
||||
<input
|
||||
type="number" min={0} step="0.01" inputMode="decimal"
|
||||
type="text" inputMode="decimal"
|
||||
value={offer[v.id] ?? ''}
|
||||
disabled={editing}
|
||||
onChange={e => setOffer(prev => ({ ...prev, [v.id]: e.target.value }))}
|
||||
@@ -199,7 +205,13 @@ export function PricingSection({ productId }: { productId: string }) {
|
||||
<select
|
||||
value={vatRate[v.id] ?? 'general'}
|
||||
disabled={editing}
|
||||
onChange={e => setVatRate(prev => ({ ...prev, [v.id]: e.target.value as 'general' | 'reduced' | 'super-reduced' }))}
|
||||
onChange={e => {
|
||||
const newVr = e.target.value as 'general' | 'reduced' | 'super-reduced';
|
||||
setVatRate(prev => ({ ...prev, [v.id]: newVr }));
|
||||
const newRateP = activeTaxRates.find(r => r.appliesTo === newVr)?.ratePercent ?? 21;
|
||||
const newGrossCents = Math.round(netCents * (1 + newRateP / 100));
|
||||
setGross(prev => ({ ...prev, [v.id]: centsToEur(newGrossCents) }));
|
||||
}}
|
||||
className="px-2 py-1 border border-gray-300 rounded-lg text-xs focus:ring-1 focus:ring-[#2D6A4F] outline-none disabled:opacity-50"
|
||||
>
|
||||
{activeTaxRates.length === 0 && (
|
||||
@@ -232,10 +244,16 @@ export function PricingSection({ productId }: { productId: string }) {
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-gray-400 text-xs">€</span>
|
||||
<input
|
||||
type="number" min={0} step="0.01" inputMode="decimal"
|
||||
type="text" inputMode="decimal"
|
||||
value={net[v.id] ?? ''}
|
||||
disabled={editing}
|
||||
onChange={e => setNet(prev => ({ ...prev, [v.id]: e.target.value }))}
|
||||
onChange={e => {
|
||||
const raw = e.target.value;
|
||||
setNet(prev => ({ ...prev, [v.id]: raw }));
|
||||
const netCentsIn = eurToCents(raw);
|
||||
const newGrossCents = Math.round(netCentsIn * (1 + ratePercent / 100));
|
||||
setGross(prev => ({ ...prev, [v.id]: centsToEur(newGrossCents) }));
|
||||
}}
|
||||
className="w-24 px-2 py-1 border border-gray-300 rounded-lg text-xs focus:ring-1 focus:ring-[#2D6A4F] outline-none disabled:opacity-50"
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user