webmoney/frontend/src/components/business/ProductSheetsTab.jsx

344 lines
15 KiB
JavaScript

import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { productSheetService } from '../../services/api';
import useFormatters from '../../hooks/useFormatters';
import ProductSheetModal from './ProductSheetModal';
import ConfirmModal from '../ConfirmModal';
const ProductSheetsTab = ({ sheets, settings, onCreated, onUpdated, onDeleted }) => {
const { t } = useTranslation();
const { currency } = useFormatters();
const [showModal, setShowModal] = useState(false);
const [editingSheet, setEditingSheet] = useState(null);
const [deleting, setDeleting] = useState(null);
const [filter, setFilter] = useState({ category: '', active: 'all' });
const [confirmModal, setConfirmModal] = useState({ show: false, sheet: null });
const handleCreate = () => {
setEditingSheet(null);
setShowModal(true);
};
const handleEdit = (sheet) => {
setEditingSheet(sheet);
setShowModal(true);
};
const handleDuplicate = async (sheet) => {
try {
const duplicated = await productSheetService.duplicate(sheet.id);
onCreated(duplicated);
} catch (err) {
alert(err.response?.data?.message || t('common.error'));
}
};
const handleDelete = async (sheet) => {
setConfirmModal({ show: true, sheet });
};
const executeDelete = async () => {
const sheet = confirmModal.sheet;
setConfirmModal({ show: false, sheet: null });
setDeleting(sheet.id);
try {
await productSheetService.delete(sheet.id);
onDeleted(sheet.id);
} catch (err) {
alert(err.response?.data?.message || t('common.error'));
} finally {
setDeleting(null);
}
};
const handleSave = (savedSheet) => {
if (editingSheet) {
onUpdated(savedSheet);
} else {
onCreated(savedSheet);
}
setShowModal(false);
};
// Filtrar fichas
const filteredSheets = sheets.filter(sheet => {
if (filter.category && sheet.category !== filter.category) return false;
if (filter.active === 'active' && !sheet.is_active) return false;
if (filter.active === 'inactive' && sheet.is_active) return false;
return true;
});
// Categorias únicas
const categories = [...new Set(sheets.map(s => s.category).filter(Boolean))];
return (
<>
{/* Header */}
<div className="d-flex justify-content-between align-items-center mb-4">
<div>
<h5 className="text-white mb-1">{t('business.products.title')}</h5>
<p className="text-slate-400 small mb-0">{t('business.products.description')}</p>
</div>
<button className="btn btn-primary" onClick={handleCreate}>
<i className="bi bi-plus-lg me-2"></i>
{t('business.products.add')}
</button>
</div>
{/* Filtros */}
{sheets.length > 0 && (
<div className="row g-3 mb-4">
<div className="col-auto">
<select
className="form-select form-select-sm bg-dark text-white border-secondary"
value={filter.category}
onChange={(e) => setFilter(prev => ({ ...prev, category: e.target.value }))}
>
<option value="">{t('business.products.allCategories')}</option>
{categories.map(cat => (
<option key={cat} value={cat}>{cat}</option>
))}
</select>
</div>
<div className="col-auto">
<select
className="form-select form-select-sm bg-dark text-white border-secondary"
value={filter.active}
onChange={(e) => setFilter(prev => ({ ...prev, active: e.target.value }))}
>
<option value="all">{t('common.all')}</option>
<option value="active">{t('common.active')}</option>
<option value="inactive">{t('common.inactive')}</option>
</select>
</div>
</div>
)}
{/* Empty State */}
{sheets.length === 0 ? (
<div className="card border-0" style={{ background: '#1e293b' }}>
<div className="card-body text-center py-5">
<i className="bi bi-box-seam fs-1 text-slate-500 mb-3 d-block"></i>
<h5 className="text-white mb-2">{t('business.products.empty')}</h5>
<p className="text-slate-400 mb-4">{t('business.products.emptyDescription')}</p>
<button className="btn btn-primary" onClick={handleCreate}>
<i className="bi bi-plus-lg me-2"></i>
{t('business.products.createFirst')}
</button>
</div>
</div>
) : filteredSheets.length === 0 ? (
<div className="text-center py-5">
<i className="bi bi-funnel fs-1 text-slate-500 mb-3 d-block"></i>
<p className="text-slate-400">{t('business.products.noResults')}</p>
</div>
) : (
/* Products Grid */
<div className="row g-4">
{filteredSheets.map(sheet => (
<div key={sheet.id} className="col-12 col-md-6 col-xl-4">
<div
className="card border-0 h-100"
style={{
background: sheet.is_active ? '#1e293b' : '#0f172a',
opacity: sheet.is_active ? 1 : 0.7,
}}
>
<div className="card-header border-0 d-flex justify-content-between align-items-start py-3" style={{ background: 'transparent' }}>
<div>
<h6 className="text-white mb-1">
{sheet.name}
{!sheet.is_active && (
<span className="badge bg-secondary ms-2">{t('common.inactive')}</span>
)}
</h6>
{sheet.sku && <small className="text-slate-500">{t('business.common.skuLabel')}: {sheet.sku}</small>}
{sheet.category && (
<span className="badge bg-secondary ms-2" style={{ fontSize: '10px' }}>{sheet.category}</span>
)}
</div>
<div className="dropdown">
<button className="btn btn-sm btn-outline-secondary border-0" data-bs-toggle="dropdown">
<i className="bi bi-three-dots-vertical"></i>
</button>
<ul className="dropdown-menu dropdown-menu-end" style={{ background: '#1e293b' }}>
<li>
<button className="dropdown-item text-white" onClick={() => handleEdit(sheet)}>
<i className="bi bi-pencil me-2"></i>
{t('common.edit')}
</button>
</li>
<li>
<button className="dropdown-item text-white" onClick={() => handleDuplicate(sheet)}>
<i className="bi bi-copy me-2"></i>
{t('business.products.duplicate')}
</button>
</li>
<li><hr className="dropdown-divider" style={{ borderColor: 'rgba(255,255,255,0.1)' }} /></li>
<li>
<button
className="dropdown-item text-danger"
onClick={() => handleDelete(sheet)}
disabled={deleting === sheet.id}
>
<i className="bi bi-trash me-2"></i>
{t('common.delete')}
</button>
</li>
</ul>
</div>
</div>
<div className="card-body py-3">
{/* CMV e Preço de Venda */}
<div className="row g-2 mb-3">
<div className="col-6">
<div className="p-2 rounded text-center" style={{ background: 'rgba(239, 68, 68, 0.1)' }}>
<small className="text-slate-500 d-block">{t('business.common.cmvLabel')}</small>
<span className="text-danger fw-bold">
{currency(sheet.cmv_total, sheet.currency)}
</span>
</div>
</div>
<div className="col-6">
<div className="p-2 rounded text-center" style={{ background: 'rgba(16, 185, 129, 0.1)' }}>
<small className="text-slate-500 d-block">{t('business.products.salePrice')}</small>
<span className="text-success fw-bold">
{sheet.sale_price ? currency(sheet.sale_price, sheet.currency) : '-'}
</span>
</div>
</div>
{/* Final Price (if strategic pricing applied) */}
{sheet.final_price && parseFloat(sheet.final_price) !== parseFloat(sheet.sale_price) && (
<div className="col-4 text-end">
<div className="p-2 rounded" style={{ background: 'rgba(147, 51, 234, 0.1)' }}>
<small className="text-slate-500 d-block">{t('business.products.finalPrice')}</small>
<span className="text-purple fw-bold" style={{ color: '#a855f7' }}>
{currency(sheet.final_price, sheet.currency)}
</span>
</div>
</div>
)}
</div>
{/* Margem de Contribuição */}
{sheet.contribution_margin !== null && (
<div className="d-flex justify-content-between align-items-center small mb-3 p-2 rounded" style={{ background: 'rgba(255,255,255,0.05)' }}>
<span className="text-slate-500">{t('business.products.contributionMargin')}</span>
<span className="text-info">
{currency(sheet.contribution_margin, sheet.currency)}
<span className="text-slate-500 ms-1">({sheet.contribution_margin_percent}%)</span>
{sheet.real_margin_percent && parseFloat(sheet.real_margin_percent) !== parseFloat(sheet.contribution_margin_percent) && (
<span className="ms-2 text-purple" style={{ color: '#a855f7' }}>
{parseFloat(sheet.real_margin_percent).toFixed(1)}%
</span>
)}
</span>
</div>
)}
{/* Competitor Comparison */}
{sheet.competitor_comparison && (
<div className="d-flex justify-content-between align-items-center small mb-3 p-2 rounded"
style={{ background: sheet.competitor_comparison.position === 'below' ? 'rgba(34, 197, 94, 0.1)' : 'rgba(239, 68, 68, 0.1)' }}>
<span className="text-slate-500">
<i className="bi bi-building me-1"></i>
{t('business.products.competitorComparison')}
</span>
<span className={sheet.competitor_comparison.position === 'below' ? 'text-success' : 'text-danger'}>
{currency(sheet.competitor_comparison.our_price, sheet.currency)}
<span className="ms-1">
({sheet.competitor_comparison.percent_difference > 0 ? '+' : ''}{sheet.competitor_comparison.percent_difference.toFixed(1)}%)
</span>
</span>
</div>
)}
{/* Strategic Badges */}
{(sheet.price_strategy !== 'neutral' || sheet.psychological_rounding || sheet.target_margin_percent) && (
<div className="mb-3">
{sheet.price_strategy === 'aggressive' && (
<span className="badge bg-danger me-1">
<i className="bi bi-lightning me-1"></i>{t('business.products.strategyAggressiveLabel')}
</span>
)}
{sheet.price_strategy === 'premium' && (
<span className="badge bg-warning text-dark me-1">
<i className="bi bi-star me-1"></i>{t('business.products.strategyPremiumLabel')}
</span>
)}
{sheet.psychological_rounding && (
<span className="badge bg-info me-1">
<i className="bi bi-magic me-1"></i>{t('business.products.psychologicalBadge')}
</span>
)}
{sheet.target_margin_percent && (
<span className="badge bg-secondary me-1">
<i className="bi bi-percent me-1"></i>{sheet.target_margin_percent}%
</span>
)}
</div>
)}
{/* Itens/Componentes */}
{sheet.items && sheet.items.length > 0 && (
<div className="small">
<small className="text-slate-500 d-block mb-2">{t('business.products.components')} ({sheet.items.length})</small>
<div style={{ maxHeight: '100px', overflowY: 'auto' }}>
{sheet.items.map(item => (
<div key={item.id} className="d-flex justify-content-between py-1" style={{ borderBottom: '1px solid rgba(255,255,255,0.05)' }}>
<span className="text-slate-400 text-truncate" style={{ maxWidth: '60%' }}>{item.name}</span>
<span className="text-white">{currency(item.unit_cost, sheet.currency)}</span>
</div>
))}
</div>
</div>
)}
{/* Configuração usada */}
{sheet.business_setting && (
<div className="mt-3 pt-2" style={{ borderTop: '1px solid rgba(255,255,255,0.1)' }}>
<small className="text-slate-500">
<i className="bi bi-gear me-1"></i>
{sheet.business_setting.name}
{sheet.markup_used && (
<span className="ms-2">
({t('business.common.markupLabel')}: {parseFloat(sheet.markup_used).toFixed(2)})
</span>
)}
</small>
</div>
)}
</div>
</div>
</div>
))}
</div>
)}
{/* Modal */}
{showModal && (
<ProductSheetModal
sheet={editingSheet}
settings={settings}
onSave={handleSave}
onClose={() => setShowModal(false)}
/>
)}
{/* Confirm Modal */}
<ConfirmModal
show={confirmModal.show}
message={t('business.products.confirmDelete')}
variant="danger"
onConfirm={executeDelete}
onCancel={() => setConfirmModal({ show: false, sheet: null })}
/>
</>
);
};
export default ProductSheetsTab;