feat: Restructure receipt form UI with bon fiscal sections
- Restructure OCRPreview with 5 sections matching Romanian receipt format: FURNIZOR, DOCUMENT, TOTAL, PLATA, TVA - Add collapse button to OCRPreview header with minimize functionality - Restructure form with matching compact sections for side-by-side comparison - Add "din OCR" indicator when payment mode is auto-set from OCR - Remove auto-collapse behavior - OCRPreview stays visible after applying data - Add prominent TOTAL box styling with gradient background - Compact form layout with fields on same row where logical - Add TVA section to form with editable breakdown 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,117 +7,107 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<span class="overall-confidence">
|
<span class="overall-confidence">
|
||||||
Incredere generala:
|
|
||||||
<OCRConfidenceIndicator
|
<OCRConfidenceIndicator
|
||||||
:confidence="data.overall_confidence"
|
:confidence="data.overall_confidence"
|
||||||
:show-percentage="true"
|
:show-percentage="true"
|
||||||
size="normal"
|
size="normal"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
<Button
|
||||||
|
icon="pi pi-minus"
|
||||||
|
text
|
||||||
|
rounded
|
||||||
|
size="small"
|
||||||
|
@click="$emit('collapse')"
|
||||||
|
v-tooltip="'Minimizeaza'"
|
||||||
|
class="collapse-btn"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="preview-content">
|
<div class="preview-content">
|
||||||
<div class="preview-grid">
|
<!-- SECTION: FURNIZOR -->
|
||||||
<!-- Receipt Type -->
|
<div class="ocr-section" v-if="data.partner_name || data.cui || data.address">
|
||||||
<div class="preview-field" v-if="data.receipt_type">
|
<div class="ocr-section-title">FURNIZOR</div>
|
||||||
<label>Tip Document</label>
|
<div class="ocr-section-content">
|
||||||
<div class="field-value">
|
<div class="vendor-name" v-if="data.partner_name">
|
||||||
|
{{ data.partner_name }}
|
||||||
|
<OCRConfidenceIndicator :confidence="data.confidence_vendor" size="small" />
|
||||||
|
</div>
|
||||||
|
<div class="vendor-cui" v-if="data.cui">CUI: {{ data.cui }}</div>
|
||||||
|
<div class="vendor-address" v-if="data.address">{{ data.address }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SECTION: DOCUMENT -->
|
||||||
|
<div class="ocr-section" v-if="data.receipt_type || data.receipt_number || data.receipt_date">
|
||||||
|
<div class="ocr-section-title">DOCUMENT</div>
|
||||||
|
<div class="ocr-section-content">
|
||||||
|
<div class="document-row">
|
||||||
<Tag
|
<Tag
|
||||||
|
v-if="data.receipt_type"
|
||||||
:value="data.receipt_type === 'bon_fiscal' ? 'Bon Fiscal' : 'Chitanta'"
|
:value="data.receipt_type === 'bon_fiscal' ? 'Bon Fiscal' : 'Chitanta'"
|
||||||
:severity="data.receipt_type === 'bon_fiscal' ? 'info' : 'success'"
|
:severity="data.receipt_type === 'bon_fiscal' ? 'info' : 'success'"
|
||||||
/>
|
/>
|
||||||
|
<span v-if="data.receipt_number" class="doc-number">
|
||||||
|
Nr: {{ data.receipt_series ? data.receipt_series + ' ' : '' }}{{ data.receipt_number }}
|
||||||
|
</span>
|
||||||
|
<span v-if="data.receipt_date" class="doc-date">
|
||||||
|
<i class="pi pi-calendar"></i>
|
||||||
|
{{ formatDate(data.receipt_date) }}
|
||||||
|
<OCRConfidenceIndicator :confidence="data.confidence_date" size="small" />
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Amount -->
|
<!-- SECTION: TOTAL -->
|
||||||
<div class="preview-field" v-if="data.amount">
|
<div class="ocr-section" v-if="data.amount">
|
||||||
<label>
|
<div class="ocr-section-title">TOTAL</div>
|
||||||
Suma
|
<div class="ocr-section-content">
|
||||||
|
<div class="ocr-total-box">
|
||||||
|
<span class="total-amount">{{ formatAmount(data.amount) }} LEI</span>
|
||||||
<OCRConfidenceIndicator :confidence="data.confidence_amount" size="small" />
|
<OCRConfidenceIndicator :confidence="data.confidence_amount" size="small" />
|
||||||
</label>
|
</div>
|
||||||
<div class="field-value amount">
|
<div v-if="data.items_count" class="items-count">
|
||||||
{{ formatAmount(data.amount) }} RON
|
{{ data.items_count }} articole
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Date -->
|
<!-- SECTION: PLATA -->
|
||||||
<div class="preview-field" v-if="data.receipt_date">
|
<div class="ocr-section" v-if="data.payment_methods?.length > 0">
|
||||||
<label>
|
<div class="ocr-section-title">PLATA</div>
|
||||||
Data
|
<div class="ocr-section-content">
|
||||||
<OCRConfidenceIndicator :confidence="data.confidence_date" size="small" />
|
<div class="ocr-payment-tags">
|
||||||
</label>
|
|
||||||
<div class="field-value">
|
|
||||||
{{ formatDate(data.receipt_date) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Receipt Number -->
|
|
||||||
<div class="preview-field" v-if="data.receipt_number">
|
|
||||||
<label>Numar Bon</label>
|
|
||||||
<div class="field-value">
|
|
||||||
{{ data.receipt_series ? data.receipt_series + ' ' : '' }}{{ data.receipt_number }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Vendor -->
|
|
||||||
<div class="preview-field full-width" v-if="data.partner_name">
|
|
||||||
<label>
|
|
||||||
Furnizor
|
|
||||||
<OCRConfidenceIndicator :confidence="data.confidence_vendor" size="small" />
|
|
||||||
</label>
|
|
||||||
<div class="field-value">
|
|
||||||
{{ data.partner_name }}
|
|
||||||
<span v-if="data.cui" class="cui-badge">CUI: {{ data.cui }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- TVA Entries (multiple rates) -->
|
|
||||||
<div class="preview-field full-width" v-if="data.tva_entries?.length > 0 || data.tva_total">
|
|
||||||
<label>TVA</label>
|
|
||||||
<div class="tva-breakdown">
|
|
||||||
<div v-for="(entry, idx) in data.tva_entries" :key="idx" class="tva-entry">
|
|
||||||
<span class="tva-code" v-if="entry.code">{{ entry.code }}</span>
|
|
||||||
<span class="tva-percent-badge">{{ entry.percent }}%</span>
|
|
||||||
<span class="tva-amount">{{ formatAmount(entry.amount) }} RON</span>
|
|
||||||
</div>
|
|
||||||
<div v-if="data.tva_total && data.tva_entries?.length > 1" class="tva-total">
|
|
||||||
<strong>Total TVA:</strong> {{ formatAmount(data.tva_total) }} RON
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Payment Methods from OCR -->
|
|
||||||
<div class="preview-field full-width" v-if="data.payment_methods?.length > 0">
|
|
||||||
<label>Modalitati Plata (OCR)</label>
|
|
||||||
<div class="payment-methods-list">
|
|
||||||
<Tag
|
<Tag
|
||||||
v-for="(pm, idx) in data.payment_methods"
|
v-for="(pm, idx) in data.payment_methods"
|
||||||
:key="idx"
|
:key="idx"
|
||||||
:severity="pm.method === 'CARD' ? 'info' : 'success'"
|
:severity="pm.method === 'CARD' ? 'info' : 'success'"
|
||||||
:value="`${pm.method}: ${formatAmount(pm.amount)} RON`"
|
:value="`${pm.method}: ${formatAmount(pm.amount)} LEI`"
|
||||||
class="mr-1"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="data.suggested_payment_mode" class="suggested-payment-mode">
|
<div v-if="data.suggested_payment_mode" class="suggested-payment-mode">
|
||||||
<i class="pi pi-lightbulb" style="color: #f59e0b;"></i>
|
<i class="pi pi-lightbulb"></i>
|
||||||
<span>Sugestie: <strong>{{ getSuggestedPaymentLabel(data.suggested_payment_mode) }}</strong></span>
|
<span>Sugestie: <strong>{{ getSuggestedPaymentLabel(data.suggested_payment_mode) }}</strong></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Items Count -->
|
<!-- SECTION: TVA -->
|
||||||
<div class="preview-field" v-if="data.items_count">
|
<div class="ocr-section" v-if="data.tva_entries?.length > 0 || data.tva_total">
|
||||||
<label>Nr. Articole</label>
|
<div class="ocr-section-title">TVA</div>
|
||||||
<div class="field-value">
|
<div class="ocr-section-content">
|
||||||
{{ data.items_count }} articole
|
<div class="ocr-tva-table">
|
||||||
</div>
|
<div v-for="(entry, idx) in data.tva_entries" :key="idx" class="tva-row">
|
||||||
</div>
|
<span class="tva-code" v-if="entry.code">{{ entry.code }}</span>
|
||||||
|
<span class="tva-percent">({{ entry.percent }}%)</span>
|
||||||
<!-- Address -->
|
<span class="tva-amount">{{ formatAmount(entry.amount) }} LEI</span>
|
||||||
<div class="preview-field full-width" v-if="data.address">
|
</div>
|
||||||
<label>Adresa</label>
|
<div v-if="computedTvaTotal > 0" class="tva-row tva-total-row">
|
||||||
<div class="field-value address-text">
|
<span class="tva-label">Total TVA:</span>
|
||||||
{{ data.address }}
|
<span class="tva-amount">{{ formatAmount(computedTvaTotal) }} LEI</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -168,9 +158,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import OCRConfidenceIndicator from './OCRConfidenceIndicator.vue'
|
import OCRConfidenceIndicator from './OCRConfidenceIndicator.vue'
|
||||||
import Tag from 'primevue/tag'
|
import Tag from 'primevue/tag'
|
||||||
|
import Button from 'primevue/button'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
@@ -179,10 +170,17 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
defineEmits(['apply', 'dismiss'])
|
defineEmits(['apply', 'dismiss', 'collapse'])
|
||||||
|
|
||||||
const showRawText = ref(false)
|
const showRawText = ref(false)
|
||||||
|
|
||||||
|
// Computed TVA total from entries
|
||||||
|
const computedTvaTotal = computed(() => {
|
||||||
|
if (props.data.tva_total) return parseFloat(props.data.tva_total)
|
||||||
|
if (!props.data.tva_entries?.length) return 0
|
||||||
|
return props.data.tva_entries.reduce((sum, e) => sum + parseFloat(e.amount || 0), 0)
|
||||||
|
})
|
||||||
|
|
||||||
const getSuggestedPaymentLabel = (mode) => {
|
const getSuggestedPaymentLabel = (mode) => {
|
||||||
const labels = {
|
const labels = {
|
||||||
'casa': 'Casa (numerar firma)',
|
'casa': 'Casa (numerar firma)',
|
||||||
@@ -279,6 +277,12 @@ const formatProcessingTime = (ms) => {
|
|||||||
color: #166534;
|
color: #166534;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
.overall-confidence {
|
.overall-confidence {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -287,64 +291,145 @@ const formatProcessingTime = (ms) => {
|
|||||||
color: #166534;
|
color: #166534;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.collapse-btn {
|
||||||
|
color: #166534 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.preview-content {
|
.preview-content {
|
||||||
padding: 1rem;
|
padding: 0.75rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-grid {
|
/* Section-based layout (bon fiscal style) */
|
||||||
display: grid;
|
.ocr-section {
|
||||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
padding: 0.6rem 0;
|
||||||
gap: 1rem;
|
border-bottom: 1px solid #d1fae5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-field {
|
.ocr-section:last-of-type {
|
||||||
display: flex;
|
border-bottom: none;
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.25rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-field.full-width {
|
.ocr-section-title {
|
||||||
grid-column: 1 / -1;
|
font-size: 0.7rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #166534;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
margin-bottom: 0.35rem;
|
||||||
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-field label {
|
.ocr-section-content {
|
||||||
font-size: 0.8rem;
|
|
||||||
color: #64748b;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-value {
|
|
||||||
font-weight: 500;
|
|
||||||
color: #1e293b;
|
color: #1e293b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.field-value.amount {
|
/* FURNIZOR section */
|
||||||
font-size: 1.25rem;
|
.vendor-name {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vendor-cui {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #475569;
|
||||||
|
margin-top: 0.15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vendor-address {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #64748b;
|
||||||
|
margin-top: 0.15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DOCUMENT section */
|
||||||
|
.document-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-number {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-date {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
color: #475569;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-date .pi-calendar {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TOTAL section - prominent */
|
||||||
|
.ocr-total-box {
|
||||||
|
background: linear-gradient(135deg, #dcfce7 0%, #bbf7d0 100%);
|
||||||
|
border: 2px solid #86efac;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-amount {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
color: #166534;
|
color: #166534;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cui-badge {
|
.items-count {
|
||||||
display: inline-block;
|
text-align: center;
|
||||||
margin-left: 0.5rem;
|
|
||||||
padding: 0.15rem 0.5rem;
|
|
||||||
background: #e2e8f0;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
color: #475569;
|
color: #64748b;
|
||||||
|
margin-top: 0.35rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tva-breakdown {
|
/* PLATA section */
|
||||||
|
.ocr-payment-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggested-payment-mode {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
padding: 0.4rem 0.6rem;
|
||||||
|
background: #fef3c7;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #92400e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggested-payment-mode .pi-lightbulb {
|
||||||
|
color: #f59e0b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TVA section */
|
||||||
|
.ocr-tva-table {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tva-entry {
|
.tva-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tva-code {
|
.tva-code {
|
||||||
@@ -353,49 +438,25 @@ const formatProcessingTime = (ms) => {
|
|||||||
min-width: 1rem;
|
min-width: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tva-percent-badge {
|
.tva-percent {
|
||||||
display: inline-block;
|
color: #64748b;
|
||||||
padding: 0.15rem 0.5rem;
|
|
||||||
background: #dbeafe;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
color: #1e40af;
|
|
||||||
min-width: 2.5rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tva-amount {
|
.tva-amount {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tva-total {
|
.tva-total-row {
|
||||||
margin-top: 0.25rem;
|
margin-top: 0.25rem;
|
||||||
padding-top: 0.25rem;
|
padding-top: 0.25rem;
|
||||||
border-top: 1px dashed #cbd5e1;
|
border-top: 1px dashed #86efac;
|
||||||
}
|
}
|
||||||
|
|
||||||
.payment-methods-list {
|
.tva-label {
|
||||||
display: flex;
|
font-weight: 600;
|
||||||
flex-wrap: wrap;
|
color: #166534;
|
||||||
gap: 0.5rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.suggested-payment-mode {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
padding: 0.5rem;
|
|
||||||
background: #fef3c7;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
color: #92400e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.address-text {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #475569;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.raw-text-section {
|
.raw-text-section {
|
||||||
|
|||||||
@@ -15,318 +15,352 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form @submit.prevent="saveReceipt">
|
<form @submit.prevent="saveReceipt">
|
||||||
<!-- OCR Upload Section (for both create and edit modes) -->
|
<div class="receipt-form-layout">
|
||||||
<div class="upload-section">
|
<!-- COLOANA STÂNGA: Upload & OCR -->
|
||||||
<h3>
|
<div class="form-column-left">
|
||||||
<i class="pi pi-camera"></i>
|
<div class="upload-section">
|
||||||
{{ isEditMode ? 'Re-scanare OCR (optional)' : 'Poza Bon (obligatoriu)' }}
|
<h3>
|
||||||
</h3>
|
<i class="pi pi-camera"></i>
|
||||||
|
{{ isEditMode ? 'Re-scanare OCR (optional)' : 'Poza Bon' }}
|
||||||
|
</h3>
|
||||||
|
|
||||||
<!-- OCR Upload Zone -->
|
<!-- OCR Upload Zone -->
|
||||||
<OCRUploadZone
|
<OCRUploadZone
|
||||||
ref="ocrUploadZone"
|
ref="ocrUploadZone"
|
||||||
@ocr-result="onOCRResult"
|
@ocr-result="onOCRResult"
|
||||||
@file-selected="onOCRFileSelected"
|
@file-selected="onOCRFileSelected"
|
||||||
@error="onOCRError"
|
@error="onOCRError"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- OCR Preview (when results are available) -->
|
<!-- OCR Applied Banner (collapsed state) -->
|
||||||
<OCRPreview
|
<div
|
||||||
v-if="ocrData"
|
v-if="ocrData && ocrCollapsed"
|
||||||
:data="ocrData"
|
class="ocr-applied-banner"
|
||||||
@apply="applyOCRData"
|
@click="ocrCollapsed = false"
|
||||||
@dismiss="dismissOCRData"
|
>
|
||||||
/>
|
<i class="pi pi-check-circle"></i>
|
||||||
</div>
|
<span>Date OCR aplicate</span>
|
||||||
|
<i class="pi pi-chevron-down"></i>
|
||||||
<!-- Standard Upload Section (for edit mode or additional files) -->
|
</div>
|
||||||
<div class="upload-section" v-if="isEditMode || selectedFiles.length > 0">
|
|
||||||
<h3 v-if="isEditMode">
|
<!-- OCR Preview (expanded state) -->
|
||||||
<i class="pi pi-camera"></i>
|
<OCRPreview
|
||||||
Poza Bon (obligatoriu)
|
v-if="ocrData && !ocrCollapsed"
|
||||||
</h3>
|
:data="ocrData"
|
||||||
<h3 v-else-if="selectedFiles.length > 0">
|
@apply="applyOCRData"
|
||||||
<i class="pi pi-paperclip"></i>
|
@dismiss="dismissOCRData"
|
||||||
Fisiere Selectate
|
@collapse="ocrCollapsed = true"
|
||||||
</h3>
|
/>
|
||||||
|
</div>
|
||||||
<FileUpload
|
|
||||||
v-if="isEditMode"
|
<!-- Standard Upload Section (for edit mode or additional files) -->
|
||||||
ref="fileUpload"
|
<div class="upload-section" v-if="isEditMode || selectedFiles.length > 0">
|
||||||
mode="advanced"
|
<h3 v-if="isEditMode">
|
||||||
:multiple="true"
|
<i class="pi pi-images"></i>
|
||||||
accept="image/*,application/pdf"
|
Atasamente
|
||||||
:maxFileSize="10000000"
|
</h3>
|
||||||
@select="onFileSelect"
|
<h3 v-else-if="selectedFiles.length > 0">
|
||||||
@remove="onFileRemove"
|
<i class="pi pi-paperclip"></i>
|
||||||
:auto="false"
|
Fisiere Selectate
|
||||||
:showUploadButton="false"
|
</h3>
|
||||||
:showCancelButton="false"
|
|
||||||
>
|
<FileUpload
|
||||||
<template #empty>
|
v-if="isEditMode"
|
||||||
<div class="upload-area">
|
ref="fileUpload"
|
||||||
<i class="pi pi-cloud-upload" style="font-size: 3rem; color: #667eea;"></i>
|
mode="advanced"
|
||||||
<p>Trage fisierele aici sau click pentru a selecta</p>
|
:multiple="true"
|
||||||
<p style="font-size: 0.8rem; color: #888;">
|
accept="image/*,application/pdf"
|
||||||
Formate acceptate: JPG, PNG, PDF (max 10MB)
|
:maxFileSize="10000000"
|
||||||
</p>
|
@select="onFileSelect"
|
||||||
</div>
|
@remove="onFileRemove"
|
||||||
</template>
|
:auto="false"
|
||||||
</FileUpload>
|
:showUploadButton="false"
|
||||||
|
:showCancelButton="false"
|
||||||
<!-- Existing attachments (edit mode) -->
|
>
|
||||||
<div v-if="existingAttachments.length" class="image-preview-grid">
|
<template #empty>
|
||||||
<div
|
<div class="upload-area">
|
||||||
v-for="att in existingAttachments"
|
<i class="pi pi-cloud-upload" style="font-size: 2rem; color: #667eea;"></i>
|
||||||
:key="att.id"
|
<p>Trage fisierele aici</p>
|
||||||
class="image-preview-item"
|
</div>
|
||||||
>
|
</template>
|
||||||
<img
|
</FileUpload>
|
||||||
v-if="att.mime_type?.startsWith('image/')"
|
|
||||||
:src="store.getAttachmentUrl(att.id)"
|
<!-- Existing attachments (edit mode) -->
|
||||||
:alt="att.filename"
|
<div v-if="existingAttachments.length" class="image-preview-grid">
|
||||||
/>
|
<div
|
||||||
<div v-else class="pdf-preview">
|
v-for="att in existingAttachments"
|
||||||
<i class="pi pi-file-pdf" style="font-size: 3rem;"></i>
|
:key="att.id"
|
||||||
<span>{{ att.filename }}</span>
|
class="image-preview-item"
|
||||||
</div>
|
>
|
||||||
<Button
|
<img
|
||||||
icon="pi pi-times"
|
v-if="att.mime_type?.startsWith('image/')"
|
||||||
severity="danger"
|
:src="store.getAttachmentUrl(att.id)"
|
||||||
rounded
|
:alt="att.filename"
|
||||||
class="remove-btn"
|
/>
|
||||||
@click="removeExistingAttachment(att.id)"
|
<div v-else class="pdf-preview">
|
||||||
/>
|
<i class="pi pi-file-pdf" style="font-size: 2rem;"></i>
|
||||||
</div>
|
<span>{{ att.filename }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<Button
|
||||||
<!-- Selected files preview (create mode) -->
|
icon="pi pi-times"
|
||||||
<div v-if="!isEditMode && selectedFiles.length" class="selected-files-list">
|
severity="danger"
|
||||||
<div
|
rounded
|
||||||
v-for="(file, index) in selectedFiles"
|
class="remove-btn"
|
||||||
:key="index"
|
size="small"
|
||||||
class="selected-file-item"
|
@click="removeExistingAttachment(att.id)"
|
||||||
>
|
/>
|
||||||
<i :class="file.type.startsWith('image/') ? 'pi pi-image' : 'pi pi-file-pdf'"></i>
|
</div>
|
||||||
<span class="file-name">{{ file.name }}</span>
|
</div>
|
||||||
<span class="file-size">{{ formatFileSize(file.size) }}</span>
|
|
||||||
<Button
|
<!-- Selected files preview (create mode) -->
|
||||||
icon="pi pi-times"
|
<div v-if="!isEditMode && selectedFiles.length" class="selected-files-list">
|
||||||
severity="danger"
|
<div
|
||||||
rounded
|
v-for="(file, index) in selectedFiles"
|
||||||
size="small"
|
:key="index"
|
||||||
@click="removeSelectedFile(index)"
|
class="selected-file-item"
|
||||||
/>
|
>
|
||||||
</div>
|
<i :class="file.type.startsWith('image/') ? 'pi pi-image' : 'pi pi-file-pdf'"></i>
|
||||||
</div>
|
<span class="file-name">{{ file.name }}</span>
|
||||||
</div>
|
<span class="file-size">{{ formatFileSize(file.size) }}</span>
|
||||||
|
<Button
|
||||||
<Divider />
|
icon="pi pi-times"
|
||||||
|
severity="danger"
|
||||||
<!-- Receipt Details -->
|
rounded
|
||||||
<h3>
|
size="small"
|
||||||
<i class="pi pi-info-circle"></i>
|
@click="removeSelectedFile(index)"
|
||||||
Detalii Bon
|
/>
|
||||||
</h3>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-grid">
|
</div>
|
||||||
<div class="form-field">
|
</div>
|
||||||
<label>Tip Document *</label>
|
|
||||||
<div class="radio-group">
|
<!-- COLOANA DREAPTA: Formular Compact -->
|
||||||
<div class="radio-item">
|
<div class="form-column-right">
|
||||||
<RadioButton
|
<!-- SECTION: FURNIZOR -->
|
||||||
v-model="form.receipt_type"
|
<div class="form-section">
|
||||||
value="bon_fiscal"
|
<div class="form-section-title">FURNIZOR</div>
|
||||||
inputId="type_bon"
|
<div class="form-section-content">
|
||||||
/>
|
<div class="form-row">
|
||||||
<label for="type_bon">Bon Fiscal</label>
|
<div class="form-field flex-2">
|
||||||
</div>
|
<label>Furnizor</label>
|
||||||
<div class="radio-item">
|
<AutoComplete
|
||||||
<RadioButton
|
v-model="form.partner_name"
|
||||||
v-model="form.receipt_type"
|
:suggestions="filteredPartners"
|
||||||
value="chitanta"
|
optionLabel="name"
|
||||||
inputId="type_chitanta"
|
field="name"
|
||||||
/>
|
@complete="searchPartners"
|
||||||
<label for="type_chitanta">Chitanta</label>
|
@item-select="onPartnerSelect"
|
||||||
</div>
|
placeholder="Cauta furnizor..."
|
||||||
</div>
|
dropdown
|
||||||
</div>
|
:forceSelection="false"
|
||||||
|
/>
|
||||||
<div class="form-field">
|
<small v-if="supplierSource" class="p-text-success supplier-selected">
|
||||||
<label>Directie *</label>
|
<i class="pi pi-check-circle"></i>
|
||||||
<div class="radio-group">
|
Validat ({{ supplierSource }})
|
||||||
<div class="radio-item">
|
</small>
|
||||||
<RadioButton
|
</div>
|
||||||
v-model="form.direction"
|
<div class="form-field flex-1">
|
||||||
value="cheltuiala"
|
<label>CUI</label>
|
||||||
inputId="dir_cheltuiala"
|
<InputText v-model="form.cui" placeholder="RO12345678" />
|
||||||
/>
|
<small v-if="supplierWarning.show" class="p-text-warning supplier-warning">
|
||||||
<label for="dir_cheltuiala">Cheltuiala</label>
|
<i class="pi pi-exclamation-triangle"></i>
|
||||||
</div>
|
Negasit
|
||||||
<div class="radio-item">
|
</small>
|
||||||
<RadioButton
|
</div>
|
||||||
v-model="form.direction"
|
</div>
|
||||||
value="incasare"
|
<div class="form-row" v-if="form.vendor_address">
|
||||||
inputId="dir_incasare"
|
<div class="form-field flex-1">
|
||||||
/>
|
<label>Adresa</label>
|
||||||
<label for="dir_incasare">Incasare</label>
|
<InputText v-model="form.vendor_address" placeholder="Adresa furnizor" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-field">
|
|
||||||
<label>Data Bon *</label>
|
<!-- SECTION: DOCUMENT -->
|
||||||
<Calendar
|
<div class="form-section">
|
||||||
v-model="form.receipt_date"
|
<div class="form-section-title">DOCUMENT</div>
|
||||||
dateFormat="dd.mm.yy"
|
<div class="form-section-content">
|
||||||
showIcon
|
<div class="form-row">
|
||||||
required
|
<div class="form-field">
|
||||||
/>
|
<div class="radio-group-inline">
|
||||||
</div>
|
<div class="radio-item">
|
||||||
|
<RadioButton
|
||||||
<div class="form-field">
|
v-model="form.receipt_type"
|
||||||
<label>Suma (RON) *</label>
|
value="bon_fiscal"
|
||||||
<InputNumber
|
inputId="type_bon"
|
||||||
v-model="form.amount"
|
/>
|
||||||
mode="currency"
|
<label for="type_bon">Bon</label>
|
||||||
currency="RON"
|
</div>
|
||||||
locale="ro-RO"
|
<div class="radio-item">
|
||||||
:minFractionDigits="2"
|
<RadioButton
|
||||||
:maxFractionDigits="2"
|
v-model="form.receipt_type"
|
||||||
required
|
value="chitanta"
|
||||||
/>
|
inputId="type_chitanta"
|
||||||
</div>
|
/>
|
||||||
|
<label for="type_chitanta">Chitanta</label>
|
||||||
<div class="form-field">
|
</div>
|
||||||
<label>Furnizor</label>
|
</div>
|
||||||
<AutoComplete
|
</div>
|
||||||
v-model="form.partner_name"
|
<div class="form-field">
|
||||||
:suggestions="filteredPartners"
|
<label>Nr.</label>
|
||||||
optionLabel="name"
|
<InputText v-model="form.receipt_number" placeholder="12345" style="max-width: 120px;" />
|
||||||
field="name"
|
</div>
|
||||||
@complete="searchPartners"
|
<div class="form-field">
|
||||||
@item-select="onPartnerSelect"
|
<label>Data *</label>
|
||||||
placeholder="Cauta furnizor..."
|
<Calendar
|
||||||
dropdown
|
v-model="form.receipt_date"
|
||||||
:forceSelection="false"
|
dateFormat="dd.mm.yy"
|
||||||
/>
|
showIcon
|
||||||
<small v-if="supplierSource" class="p-text-success supplier-selected">
|
required
|
||||||
<i class="pi pi-check-circle"></i>
|
/>
|
||||||
Validat ({{ supplierSource }})
|
</div>
|
||||||
</small>
|
</div>
|
||||||
</div>
|
<div class="form-row">
|
||||||
|
<div class="form-field">
|
||||||
<div class="form-field">
|
<div class="radio-group-inline">
|
||||||
<label>CUI (Cod Fiscal)</label>
|
<div class="radio-item">
|
||||||
<InputText v-model="form.cui" placeholder="Ex: RO12345678" />
|
<RadioButton
|
||||||
<small v-if="supplierWarning.show" class="p-text-warning supplier-warning">
|
v-model="form.direction"
|
||||||
<i class="pi pi-exclamation-triangle"></i>
|
value="cheltuiala"
|
||||||
CUI {{ supplierWarning.cui }} negasit in nomenclator
|
inputId="dir_cheltuiala"
|
||||||
</small>
|
/>
|
||||||
</div>
|
<label for="dir_cheltuiala">Cheltuiala</label>
|
||||||
|
</div>
|
||||||
<div class="form-field">
|
<div class="radio-item">
|
||||||
<label>Tip Cheltuiala *</label>
|
<RadioButton
|
||||||
<Dropdown
|
v-model="form.direction"
|
||||||
v-model="form.expense_type_code"
|
value="incasare"
|
||||||
:options="expenseTypes"
|
inputId="dir_incasare"
|
||||||
optionLabel="name"
|
/>
|
||||||
optionValue="code"
|
<label for="dir_incasare">Incasare</label>
|
||||||
placeholder="Selecteaza tip"
|
</div>
|
||||||
required
|
</div>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-field">
|
</div>
|
||||||
<label>Mod Plata</label>
|
|
||||||
<Dropdown
|
<!-- SECTION: TOTAL -->
|
||||||
v-model="form.payment_mode"
|
<div class="form-section">
|
||||||
:options="paymentModeOptions"
|
<div class="form-section-title">TOTAL</div>
|
||||||
optionLabel="label"
|
<div class="form-section-content">
|
||||||
optionValue="value"
|
<div class="form-row">
|
||||||
placeholder="Selecteaza mod plata"
|
<div class="form-field">
|
||||||
showClear
|
<label>Suma *</label>
|
||||||
/>
|
<InputNumber
|
||||||
<small class="field-hint text-secondary" v-if="!form.payment_mode">
|
v-model="form.amount"
|
||||||
Obligatoriu la trimiterea pentru aprobare
|
mode="currency"
|
||||||
</small>
|
currency="RON"
|
||||||
</div>
|
locale="ro-RO"
|
||||||
|
:minFractionDigits="2"
|
||||||
<div class="form-field">
|
:maxFractionDigits="2"
|
||||||
<label>Numar Bon</label>
|
required
|
||||||
<InputText v-model="form.receipt_number" placeholder="Optional" />
|
class="amount-input"
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
<div class="form-field form-field-full">
|
<div class="form-field flex-1">
|
||||||
<label>Descriere</label>
|
<label>Tip Cheltuiala *</label>
|
||||||
<Textarea
|
<Dropdown
|
||||||
v-model="form.description"
|
v-model="form.expense_type_code"
|
||||||
rows="3"
|
:options="expenseTypes"
|
||||||
placeholder="Descriere optionala..."
|
optionLabel="name"
|
||||||
/>
|
optionValue="code"
|
||||||
</div>
|
placeholder="Selecteaza tip"
|
||||||
</div>
|
required
|
||||||
|
/>
|
||||||
<!-- Detalii Suplimentare (populated from OCR) -->
|
</div>
|
||||||
<div v-if="form.tva_breakdown?.length > 0 || form.items_count || form.vendor_address || form.payment_methods?.length > 0" class="extra-details-section">
|
</div>
|
||||||
<h3>
|
<div class="form-row" v-if="form.items_count">
|
||||||
<i class="pi pi-list"></i>
|
<div class="form-field">
|
||||||
Detalii Suplimentare (din OCR)
|
<label>Nr. Articole</label>
|
||||||
</h3>
|
<InputNumber
|
||||||
|
v-model="form.items_count"
|
||||||
<!-- TVA Breakdown -->
|
:min="1"
|
||||||
<div class="form-field form-field-full" v-if="form.tva_breakdown?.length > 0">
|
placeholder="17"
|
||||||
<label>Defalcare TVA</label>
|
style="max-width: 100px;"
|
||||||
<div class="tva-table">
|
/>
|
||||||
<div v-for="(entry, idx) in form.tva_breakdown" :key="idx" class="tva-row">
|
</div>
|
||||||
<span class="tva-label">TVA {{ entry.code }} ({{ entry.percent }}%):</span>
|
</div>
|
||||||
<InputNumber
|
<div class="form-row">
|
||||||
v-model="form.tva_breakdown[idx].amount"
|
<div class="form-field flex-1">
|
||||||
mode="currency"
|
<label>Descriere</label>
|
||||||
currency="RON"
|
<Textarea
|
||||||
locale="ro-RO"
|
v-model="form.description"
|
||||||
:minFractionDigits="2"
|
rows="2"
|
||||||
class="tva-input"
|
placeholder="Descriere optionala..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="tva-row total" v-if="form.tva_breakdown.length > 0">
|
</div>
|
||||||
<span class="tva-label"><strong>Total TVA:</strong></span>
|
</div>
|
||||||
<span class="tva-value">{{ formatTvaTotal() }} RON</span>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
<!-- SECTION: PLATA -->
|
||||||
</div>
|
<div class="form-section">
|
||||||
|
<div class="form-section-title">PLATA</div>
|
||||||
<!-- Payment Methods (from OCR) -->
|
<div class="form-section-content">
|
||||||
<div class="form-field form-field-full" v-if="form.payment_methods?.length > 0">
|
<div class="form-row">
|
||||||
<label>Modalitati Plata</label>
|
<div class="form-field flex-1">
|
||||||
<div class="payment-methods-display">
|
<label>Mod Plata</label>
|
||||||
<Tag
|
<div class="payment-field-wrapper">
|
||||||
v-for="pm in form.payment_methods"
|
<Dropdown
|
||||||
:key="pm.method"
|
v-model="form.payment_mode"
|
||||||
:severity="pm.method === 'CARD' ? 'info' : 'success'"
|
:options="paymentModeOptions"
|
||||||
:value="`${pm.method}: ${formatCurrency(pm.amount)}`"
|
optionLabel="label"
|
||||||
/>
|
optionValue="value"
|
||||||
</div>
|
placeholder="Selecteaza mod plata"
|
||||||
</div>
|
showClear
|
||||||
|
/>
|
||||||
<div class="form-grid">
|
<span v-if="paymentSetFromOCR" class="ocr-indicator">
|
||||||
<div class="form-field" v-if="form.items_count">
|
<i class="pi pi-check-circle"></i>
|
||||||
<label>Nr. Articole</label>
|
din OCR
|
||||||
<InputNumber
|
</span>
|
||||||
v-model="form.items_count"
|
</div>
|
||||||
:min="1"
|
<small class="field-hint text-secondary" v-if="!form.payment_mode">
|
||||||
placeholder="Ex: 17"
|
Obligatoriu la trimitere
|
||||||
/>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field" v-if="form.vendor_address">
|
</div>
|
||||||
<label>Adresa Furnizor</label>
|
<!-- Payment methods tags from OCR -->
|
||||||
<InputText
|
<div v-if="form.payment_methods?.length > 0" class="payment-methods-display">
|
||||||
v-model="form.vendor_address"
|
<Tag
|
||||||
placeholder="Adresa din bon"
|
v-for="pm in form.payment_methods"
|
||||||
/>
|
:key="pm.method"
|
||||||
</div>
|
:severity="pm.method === 'CARD' ? 'info' : 'success'"
|
||||||
</div>
|
:value="`${pm.method}: ${formatCurrency(pm.amount)}`"
|
||||||
|
class="mr-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SECTION: TVA -->
|
||||||
|
<div class="form-section" v-if="form.tva_breakdown?.length > 0">
|
||||||
|
<div class="form-section-title">TVA</div>
|
||||||
|
<div class="form-section-content">
|
||||||
|
<div class="tva-edit-table">
|
||||||
|
<div v-for="(entry, idx) in form.tva_breakdown" :key="idx" class="tva-edit-row">
|
||||||
|
<span class="tva-label-compact">{{ entry.code }} ({{ entry.percent }}%)</span>
|
||||||
|
<InputNumber
|
||||||
|
v-model="form.tva_breakdown[idx].amount"
|
||||||
|
mode="currency"
|
||||||
|
currency="RON"
|
||||||
|
locale="ro-RO"
|
||||||
|
:minFractionDigits="2"
|
||||||
|
class="tva-input-compact"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="tva-edit-row tva-total-row" v-if="form.tva_breakdown.length > 0">
|
||||||
|
<span class="tva-label-compact"><strong>Total TVA:</strong></span>
|
||||||
|
<span class="tva-total-value">{{ formatTvaTotal() }} RON</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- End form-column-right -->
|
||||||
</div>
|
</div>
|
||||||
|
<!-- End receipt-form-layout -->
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
@@ -457,12 +491,16 @@ const submitting = ref(false)
|
|||||||
const ocrUploadZone = ref(null)
|
const ocrUploadZone = ref(null)
|
||||||
const ocrData = ref(null)
|
const ocrData = ref(null)
|
||||||
const ocrFile = ref(null)
|
const ocrFile = ref(null)
|
||||||
|
const ocrCollapsed = ref(false)
|
||||||
|
|
||||||
// Supplier dialog refs
|
// Supplier dialog refs
|
||||||
const showCreateSupplierDialog = ref(false)
|
const showCreateSupplierDialog = ref(false)
|
||||||
const pendingSupplierData = ref(null)
|
const pendingSupplierData = ref(null)
|
||||||
const supplierWarning = ref({ show: false, cui: '', name: '' })
|
const supplierWarning = ref({ show: false, cui: '', name: '' })
|
||||||
|
|
||||||
|
// OCR indicator for payment mode
|
||||||
|
const paymentSetFromOCR = ref(false)
|
||||||
|
|
||||||
// AutoComplete support
|
// AutoComplete support
|
||||||
const filteredPartners = ref([])
|
const filteredPartners = ref([])
|
||||||
const supplierSource = ref(null) // 'local', 'synced', or null
|
const supplierSource = ref(null) // 'local', 'synced', or null
|
||||||
@@ -618,6 +656,7 @@ const applyOCRData = async (data) => {
|
|||||||
// Auto-suggest payment_mode if OCR detected CARD
|
// Auto-suggest payment_mode if OCR detected CARD
|
||||||
if (data.suggested_payment_mode) {
|
if (data.suggested_payment_mode) {
|
||||||
form.value.payment_mode = data.suggested_payment_mode
|
form.value.payment_mode = data.suggested_payment_mode
|
||||||
|
paymentSetFromOCR.value = true // Show OCR indicator
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-search supplier by CUI if available
|
// Auto-search supplier by CUI if available
|
||||||
@@ -695,8 +734,8 @@ const applyOCRData = async (data) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear OCR preview
|
// NOTE: OCRPreview rămâne vizibil pentru comparație side-by-side
|
||||||
ocrData.value = null
|
// (NU mai colapsăm automat - utilizatorul poate compara datele)
|
||||||
|
|
||||||
toast.add({
|
toast.add({
|
||||||
severity: 'success',
|
severity: 'success',
|
||||||
@@ -811,7 +850,8 @@ const formatTvaTotal = () => {
|
|||||||
|
|
||||||
const validateForm = () => {
|
const validateForm = () => {
|
||||||
// Check if we have at least one file (for new receipts)
|
// Check if we have at least one file (for new receipts)
|
||||||
if (!isEditMode.value && selectedFiles.value.length === 0) {
|
// Also check ocrFile as a fallback (file selected for OCR processing)
|
||||||
|
if (!isEditMode.value && selectedFiles.value.length === 0 && !ocrFile.value) {
|
||||||
toast.add({
|
toast.add({
|
||||||
severity: 'warn',
|
severity: 'warn',
|
||||||
summary: 'Validare',
|
summary: 'Validare',
|
||||||
@@ -953,15 +993,86 @@ const submitForReview = async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
/* 2-column layout */
|
||||||
|
.receipt-form-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(280px, 1fr) minmax(380px, 1.5fr);
|
||||||
|
gap: 1.5rem;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-column-left {
|
||||||
|
position: sticky;
|
||||||
|
top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-column-right {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-column-right h3 {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.receipt-form-layout {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
.form-column-left {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* OCR Applied Banner (collapsed state) */
|
||||||
|
.ocr-applied-banner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.6rem 1rem;
|
||||||
|
background: #dcfce7;
|
||||||
|
border: 1px solid #86efac;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ocr-applied-banner:hover {
|
||||||
|
background: #bbf7d0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ocr-applied-banner .pi-check-circle {
|
||||||
|
color: #22c55e;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ocr-applied-banner span {
|
||||||
|
flex: 1;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #166534;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ocr-applied-banner .pi-chevron-down {
|
||||||
|
color: #166534;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
.upload-section {
|
.upload-section {
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-section h3 {
|
.upload-section h3 {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 0.75rem;
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.radio-group {
|
.radio-group {
|
||||||
@@ -1031,8 +1142,8 @@ const submitForReview = async () => {
|
|||||||
|
|
||||||
/* Extra details section (TVA, items, address) */
|
/* Extra details section (TVA, items, address) */
|
||||||
.extra-details-section {
|
.extra-details-section {
|
||||||
margin-top: 1.5rem;
|
margin-top: 1rem;
|
||||||
padding: 1rem;
|
padding: 0.75rem;
|
||||||
background: #f0f9ff;
|
background: #f0f9ff;
|
||||||
border: 1px solid #bae6fd;
|
border: 1px solid #bae6fd;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@@ -1042,8 +1153,10 @@ const submitForReview = async () => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 0.75rem;
|
||||||
|
margin-top: 0;
|
||||||
color: #0284c7;
|
color: #0284c7;
|
||||||
|
font-size: 0.95rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tva-table {
|
.tva-table {
|
||||||
@@ -1142,4 +1255,163 @@ const submitForReview = async () => {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #334155;
|
color: #334155;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
COMPACT FORM SECTIONS (matching OCRPreview)
|
||||||
|
======================================== */
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
padding: 0.6rem 0;
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section:last-of-type {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section-title {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #64748b;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compact form rows */
|
||||||
|
.form-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row .form-field {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field.flex-1 {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field.flex-2 {
|
||||||
|
flex: 2;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline radio groups */
|
||||||
|
.radio-group-inline {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 0.35rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-group-inline .radio-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-group-inline .radio-item label {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Amount input styling */
|
||||||
|
.amount-input {
|
||||||
|
max-width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Payment field wrapper with OCR indicator */
|
||||||
|
.payment-field-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-field-wrapper .p-dropdown {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ocr-indicator {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #22c55e;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ocr-indicator .pi-check-circle {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TVA edit table - compact */
|
||||||
|
.tva-edit-table {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tva-edit-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tva-label-compact {
|
||||||
|
min-width: 80px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #475569;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tva-input-compact {
|
||||||
|
max-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tva-edit-row.tva-total-row {
|
||||||
|
margin-top: 0.35rem;
|
||||||
|
padding-top: 0.35rem;
|
||||||
|
border-top: 1px dashed #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tva-total-value {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #0284c7;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Payment methods display in form */
|
||||||
|
.payment-methods-display {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.form-row {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field.flex-1,
|
||||||
|
.form-field.flex-2 {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-field-wrapper {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user