feat: debit note (#172)

* feat: new file

* feat: function api debit

* feat: add route debit

* feat: new form page

* refactor: show menu debit

* refactor: add type debit note status

* feat: add i18n

* feat: add constants

* feat: add stores

* feat: layout

* feat: add function

* refactor: change name value

* feat: form select quotation

* refactor: change name url

* refactor: use form debit

* refactor: change src import

* refactor: move file form debit

* refactor: add i18n

* feat: add type debit note

* refactor: add columns

* refactor: bind value columns

* refactor: change name Table

* refactor: edit type

* refactor: bind type debit note

* refactor: bind value debit

* refactor: chame name function

* fix: calculate page

* refactor: delete table

* refactor: change name get list

* refactor: change i18n

* refactor: change name value

* refactor: bind navigate and trigger delete

* refactor: format number deciml

* refactor: add i18n

* feat: new page

* refactor: add color debit

* feat: Debit tab

#178

* feat: TableRequest

* refactor: edit type pay condition

* refactor: add i18n btn submit

* refactor: use type enum

* feat: edit layout product expansion

* refactor: bind function

* refactor: show code

* feat: add input search and select  status

* feat: paymentform

* refactor: edit type

* refactor: add manage file and edit end point

* feat: add form.ts

* refactor: send mode

* refactor: edit v-model of due date

* feat: submit create debit

* fix: status

* refactor: handle data not allow

* fix: call updateDebitNote in edit mode and simplify payload handling

* refactor: hide edit

* refactor: handle pay condition only full

* refactor: delete pay split

* refactor: add query

* refactor: handle is debit note

* refactor: handle is quotation

* refactor: add props hide

* refactor: tap payment and receipt

* refactor: add i18n

* feat: view document

* refactor: handle btn view doc

* refactor: use my remark

---------

Co-authored-by: Thanaphon Frappet <thanaphon@frappet.com>
Co-authored-by: nwpptrs <jay02499@gmail.com>
Co-authored-by: aif912752 <siripak@chamomind.com>
This commit is contained in:
Methapon Metanipat 2025-01-27 09:04:08 +07:00 committed by GitHub
parent e3c781f857
commit 79240f53b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 4172 additions and 12 deletions

View file

@ -0,0 +1,113 @@
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { QSelect } from 'quasar';
// NOTE: Import stores
import { selectFilterOptionRefMod } from 'stores/utils';
import useOptionStore from 'stores/options';
// NOTE Import Types
import { BankBook } from 'stores/branch/types';
// NOTE: Import Components
const optionStore = useOptionStore();
const bankBookOptions = ref<Record<string, unknown>[]>([]);
let bankBookFilter: (
value: string,
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
) => void;
defineProps<{
bankBook: BankBook;
index: number;
}>();
watch(
() => optionStore.globalOption,
() => {
bankBookFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption.bankBook),
bankBookOptions,
'label',
);
},
);
</script>
<template>
<fieldset class="rounded" style="border: 1px solid var(--gray-4)">
<legend>องทางท {{ index + 1 }}</legend>
<div class="border-5 full-width row" style="gap: var(--size-4)">
<div class="column q-pa-sm" style="width: fit-content">
<img
:src="bankBook.bankUrl"
class="rounded"
style="
border: 1px solid var(--gray-3);
object-fit: scale-down;
width: 1in;
height: 1in;
"
/>
</div>
<div class="column col" style="gap: var(--size-3)">
<div class="row" style="justify-content: space-between">
<div class="text-with-label">
<div>เลขบญชธนาคาร</div>
<div class="row items-start">
<img
width="25px"
height="25px"
class="q-mr-xs"
:src="`/img/bank/${bankBook.bankName}.png`"
/>
{{ bankBook.accountNumber }}
</div>
</div>
<div class="text-with-label">
<div>เลขบญชธนาคาร</div>
<div>{{ bankBook.accountNumber }}</div>
</div>
<div class="text-with-label">
<div>อบญช</div>
<div>{{ bankBook.accountName }}</div>
</div>
</div>
<div class="row">
<div class="text-with-label">
<div>สาขา</div>
<div>{{ bankBook.bankBranch }}</div>
</div>
</div>
</div>
</div>
</fieldset>
</template>
<style scoped>
legend {
background-color: white;
padding: 5px 10px;
}
.bordered-2 {
border: 2px solid var(--border-color);
}
.border-5 {
border-radius: 5px;
}
.text-with-label {
display: flex;
flex-direction: column;
flex: 1;
& > :first-child {
color: var(--gray-6);
}
}
</style>

View file

@ -0,0 +1,654 @@
<script lang="ts" setup>
import { storeToRefs } from 'pinia';
import { onMounted, nextTick, ref, watch } from 'vue';
import { precisionRound } from 'src/utils/arithmetic';
import ThaiBahtText from 'thai-baht-text';
// NOTE: Import stores
import { formatNumberDecimal } from 'stores/utils';
import { useConfigStore } from 'stores/config';
import useBranchStore from 'stores/branch';
import { baseUrl } from 'stores/utils';
import useCustomerStore from 'stores/customer';
import { DebitNotePayload, useDebitNote } from 'src/stores/debit-note';
// NOTE Import Types
import { CustomerBranch } from 'stores/customer/types';
import { BankBook, Branch } from 'stores/branch/types';
import {
CustomerBranchRelation,
Details,
ProductServiceList,
} from 'src/stores/quotations/types';
// NOTE: Import Components
import ViewPDF from './ViewPdf.vue';
import ViewHeader from './ViewHeader.vue';
import ViewFooter from './ViewFooter.vue';
import BankComponents from './BankComponents.vue';
import PrintButton from 'src/components/button/PrintButton.vue';
import { convertTemplate } from 'src/utils/string-template';
const configStore = useConfigStore();
const branchStore = useBranchStore();
const customerStore = useCustomerStore();
const debitNoteStore = useDebitNote();
const { data: config } = storeToRefs(configStore);
type Product = {
id: string;
code: string;
detail: string;
amount: number;
priceUnit: number;
discount: number;
vat: number;
value: number;
};
type SummaryPrice = {
totalPrice: number;
totalDiscount: number;
vat: number;
vatExcluded: number;
finalPrice: number;
};
const customer = ref<CustomerBranch>();
const branch = ref<Branch>();
const productList = ref<Product[]>([]);
const bankList = ref<BankBook[]>([]);
const elements = ref<HTMLElement[]>([]);
const chunks = ref<Product[][]>([[]]);
const attachmentList = ref<
{
url: string;
isImage?: boolean;
isPDF?: boolean;
}[]
>([]);
const data = ref<
DebitNotePayload & {
customerBranch: CustomerBranchRelation;
customerBranchId: string;
registeredBranchId: string;
}
>();
const productServiceList = ref<ProductServiceList[]>([]);
const summaryPrice = ref<SummaryPrice>({
totalPrice: 0,
totalDiscount: 0,
vat: 0,
vatExcluded: 0,
finalPrice: 0,
});
async function getAttachment(quotationId: string) {
const attachment = await debitNoteStore.listAttachment({
parentId: quotationId,
});
if (attachment) {
attachmentList.value = await Promise.all(
attachment.map(async (v) => {
const url = await debitNoteStore.getAttachment({
parentId: quotationId,
name: v,
});
const ft = v.substring(v.lastIndexOf('.') + 1);
return {
url,
isImage: ['png', 'jpg', 'jpeg'].includes(ft),
isPDF: ft === 'pdf',
};
}),
);
}
}
async function assignData() {
for (let i = 0; i < productList.value.length; i++) {
let el = elements.value.at(-1);
if (!el) return;
if (getHeight(el) < 500) {
chunks.value.at(-1)?.push(productList.value[i]);
} else {
chunks.value.push([]);
i--;
}
await nextTick();
}
}
function getHeight(el: HTMLElement) {
const shadow = document.createElement('div');
shadow.style.opacity = '0';
shadow.style.position = 'absolute';
shadow.style.top = '-999999px';
shadow.style.left = '-999999px';
shadow.style.pointerEvents = 'none';
document.body.appendChild(shadow);
shadow.appendChild(el.cloneNode(true));
const height = shadow.offsetHeight;
document.body.removeChild(shadow);
return height;
}
const details = ref<Details>();
enum View {
DebitNote,
Invoice,
Payment,
Receipt,
}
const view = ref<View>(View.DebitNote);
onMounted(async () => {
let str =
localStorage.getItem('debit-note-preview') ||
sessionStorage.getItem('debit-note-preview');
if (!str) return;
const obj: DebitNotePayload = JSON.parse(str);
if (obj) sessionStorage.setItem('debit-note-preview', JSON.stringify(obj));
delete localStorage['debit-note-preview'];
const parsed = JSON.parse(sessionStorage.getItem('debit-note-preview') || '');
data.value = 'data' in parsed ? parsed.data : undefined;
if (data.value) {
if (data.value.id) {
await getAttachment(data.value.id);
}
const resCustomerBranch = await customerStore.getBranchById(
data.value.customerBranchId,
);
if (resCustomerBranch) {
customer.value = resCustomerBranch;
}
details.value = {
code: parsed.meta.source.code,
createdAt: parsed.meta.source.createdAt,
createdBy: `${parsed.meta.createdBy} ${!parsed.meta.source.createdBy ? '' : parsed.meta.source.createdBy.telephoneNo}`,
payCondition: parsed.meta.source.payCondition,
contactName: parsed.meta.source.contactName,
contactTel: parsed.meta.source.contactTel,
workName: parsed.meta.source.workName,
dueDate: parsed.meta.source.dueDate,
worker: parsed.meta.selectedWorker,
};
const resBranch = await branchStore.fetchById(
data.value?.registeredBranchId,
);
if (resBranch) {
branch.value = resBranch;
bankList.value = resBranch.bank.map((v) => ({
...v,
bankUrl: `${baseUrl}/branch/${resBranch.id}/bank-qr/${v.id}?ts=${Date.now()}`,
}));
}
productServiceList.value = parsed.meta.productServicelist;
productList.value =
productServiceList.value?.map((v) => ({
id: v.product.id,
code: v.product.code,
detail: v.product.name,
amount: v.amount || 0,
priceUnit: v.pricePerUnit || 0,
discount: v.discount || 0,
vat: v.vat || 0,
value: precisionRound(
(v.pricePerUnit || 0) * v.amount -
(v.discount || 0) +
(v.product.calcVat
? ((v.pricePerUnit || 0) * v.amount - (v.discount || 0)) *
(config.value?.vat || 0.07)
: 0),
),
})) || [];
}
summaryPrice.value = (productServiceList.value || []).reduce(
(a, c) => {
const price = precisionRound((c.pricePerUnit || 0) * c.amount);
const vat = precisionRound(
((c.pricePerUnit || 0) * c.amount - (c.discount || 0)) *
(config.value?.vat || 0.07),
);
a.totalPrice = precisionRound(a.totalPrice + price);
a.totalDiscount = precisionRound(a.totalDiscount + Number(c.discount));
a.vat = c.product.calcVat ? precisionRound(a.vat + vat) : a.vat;
a.vatExcluded = c.product.calcVat
? a.vatExcluded
: precisionRound(a.vat + vat);
a.finalPrice = precisionRound(
a.totalPrice -
a.totalDiscount +
a.vat -
Number(data.value?.discount || 0),
);
return a;
},
{
totalPrice: 0,
totalDiscount: 0,
vat: 0,
vatExcluded: 0,
finalPrice: 0,
},
);
assignData();
});
watch(elements, () => {});
function print() {
window.print();
}
</script>
<template>
<div class="toolbar">
<PrintButton solid @click="print" />
</div>
<div class="row justify-between container color-debit-note">
<section class="content" v-for="chunk in chunks">
<ViewHeader
v-if="!!branch && !!customer && !!details"
:branch="branch"
:customer="customer"
:details="details"
:view="view"
/>
<span
class="q-mb-sm q-mt-md"
style="
font-weight: 800;
font-size: 16px;
color: var(--main);
display: block;
border-bottom: 2px solid var(--main);
"
>
{{ $t('preview.productList') }}
</span>
<table ref="elements" class="q-mb-sm" cellpadding="0" style="width: 100%">
<tbody class="color-tr">
<tr>
<th>{{ $t('preview.rank') }}</th>
<th>{{ $t('preview.productCode') }}</th>
<th>{{ $t('general.detail') }}</th>
<th>{{ $t('general.amount') }}</th>
<th>{{ $t('preview.pricePerUnit') }}</th>
<th>{{ $t('preview.discount') }}</th>
<th>{{ $t('preview.vat') }}</th>
<th>{{ $t('preview.value') }}</th>
</tr>
<tr v-for="(v, i) in chunk">
<td class="text-center">{{ i + 1 }}</td>
<td>{{ v.code }}</td>
<td>{{ v.detail }}</td>
<td style="text-align: right">{{ v.amount }}</td>
<td style="text-align: right">
{{ formatNumberDecimal(v.priceUnit, 2) }}
</td>
<td style="text-align: right">
{{ formatNumberDecimal(v.discount, 2) }}
</td>
<td style="text-align: right">
{{ formatNumberDecimal(v.vat, 2) }}
</td>
<td style="text-align: right">
{{ formatNumberDecimal(v.value, 2) }}
</td>
</tr>
</tbody>
</table>
<table
style="width: 40%; margin-left: auto"
class="q-mb-md"
cellpadding="0"
>
<tbody class="color-tr">
<tr>
<td>{{ $t('general.total') }}</td>
<td class="text-right">
{{ formatNumberDecimal(summaryPrice.totalPrice, 2) }}
฿
</td>
</tr>
<tr>
<td>{{ $t('general.discount') }}</td>
<td class="text-right">
{{ formatNumberDecimal(summaryPrice.totalDiscount, 2) || 0 }} ฿
</td>
</tr>
<tr>
<td>{{ $t('general.totalAfterDiscount') }}</td>
<td class="text-right">
{{
formatNumberDecimal(
summaryPrice.totalPrice - summaryPrice.totalDiscount,
2,
)
}}
฿
</td>
</tr>
<tr>
<td>{{ $t('general.totalVatExcluded') }}</td>
<td class="text-right">
{{ formatNumberDecimal(summaryPrice.vatExcluded, 2) || 0 }}
฿
</td>
</tr>
<tr>
<td>{{ $t('general.vat', { msg: '7%' }) }}</td>
<td class="text-right">
{{ formatNumberDecimal(summaryPrice.vat, 2) }} ฿
</td>
</tr>
<tr>
<td>{{ $t('general.totalVatIncluded') }}</td>
<td class="text-right">
{{
formatNumberDecimal(
summaryPrice.totalPrice -
summaryPrice.totalDiscount +
summaryPrice.vat,
2,
)
}}
฿
</td>
</tr>
<tr>
<td>{{ $t('general.discountAfterVat') }}</td>
<td class="text-right">
{{ formatNumberDecimal(data?.discount || 0, 2) }}
฿
</td>
</tr>
</tbody>
</table>
<div class="row justify-between q-mb-md" style="width: 100%">
<div
class="column set-width bg-color full-height"
style="padding: 12px"
>
({{ ThaiBahtText(summaryPrice.finalPrice) }})
</div>
<div
class="row text-right border-5 items-center"
style="width: 40%; background: var(--main); padding: 8px"
>
<span style="color: white; font-weight: 600">ยอดรวมสทธ</span>
<span
class="border-5"
style="
width: 70%;
margin-left: auto;
background: white;
padding: 4px;
"
>
{{
formatNumberDecimal(Math.max(summaryPrice.finalPrice, 0), 2) || 0
}}
฿
</span>
</div>
</div>
</section>
<section class="content">
<ViewHeader
v-if="!!branch && !!customer && !!details"
:branch="branch"
:customer="customer"
:details="details"
:view="view"
/>
<span
class="q-mb-sm q-mt-md"
style="
font-weight: 800;
font-size: 16px;
color: var(--main);
display: block;
border-bottom: 2px solid var(--main);
"
>
{{ $t('general.remark') }}
</span>
<div
class="border-5 surface-0 detail-note q-mb-md"
style="width: 100%; padding: 8px 16px; white-space: pre-wrap"
>
<div
v-html="
convertTemplate(data?.remark || '', {
'quotation-payment': {
paymentType: data?.payCondition || 'Full',
amount: summaryPrice.finalPrice,
installments: data?.paySplit,
},
'quotation-labor': {
name:
details?.worker.map(
(v, i) =>
`${i + 1}. ` +
`${v.namePrefix}. ${v.firstNameEN} ${v.lastNameEN}`.toUpperCase(),
) || [],
},
}) || '-'
"
></div>
</div>
</section>
<section class="content">
<ViewHeader
v-if="!!branch && !!customer && !!details"
:branch="branch"
:customer="customer"
:details="details"
:view="view"
/>
<span
class="q-mb-sm"
style="
font-weight: 800;
font-size: 16px;
color: var(--main);
display: block;
border-bottom: 2px solid var(--main);
"
>
{{ $t('preview.paymentMethods') }}
</span>
<article style="height: 5.8in">
<BankComponents
v-for="(bank, index) in bankList"
:index="index"
:bank-book="bank"
:key="bank.id"
/>
</article>
<ViewFooter
:data="{
name: '',
company: branch?.name || '',
buyer: '',
buyDate: '',
approveDate: '',
approver: '',
}"
/>
</section>
<section
v-for="item in attachmentList.filter((v) => v.isImage)"
class="content"
>
<q-img :src="item.url" />
</section>
<ViewPDF
v-for="item in attachmentList.filter((v) => v.isPDF)"
:url="item.url"
/>
</div>
</template>
<style scoped>
.color-debit-note {
--main: var(--cyan-7);
--main-hsl: var(--cyan-7-hsl);
}
.toolbar {
width: 100%;
position: sticky;
top: 0;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 1rem;
padding: 1rem;
background: white;
border-bottom: 1px solid var(--gray-3);
z-index: 99999;
}
table {
border-collapse: collapse;
}
th {
background: var(--main);
color: white;
padding: 4px;
}
td {
padding: 4px 8px;
}
.border-5 {
border-radius: 5px;
}
.set-width {
width: 50%;
}
.bg-color {
background-color: hsla(var(--main-hsl) / 0.1);
}
.color-tr > tr:nth-child(odd) {
background-color: hsla(var(--main-hsl) / 0.1);
}
.container {
padding: 1rem;
display: flex;
gap: 1rem;
margin-inline: auto;
background: var(--gray-3);
width: calc(8.3in + 1rem);
}
.container :deep(*) {
font-size: 95%;
}
.content {
width: 100%;
padding: 0.5in;
align-items: center;
background: white;
page-break-after: always;
break-after: page;
height: 11.7in;
max-height: 11.7in;
}
.position-bottom {
margin-top: auto;
}
.detail-note {
display: flex;
flex-direction: column;
gap: 8px;
& > * {
display: flex;
flex-direction: column;
}
}
hr {
border-style: solid;
border-color: var(--main);
}
@media print {
.toolbar {
display: none;
}
.container {
padding: 0;
gap: 0;
width: 100%;
background: white;
}
.content {
padding: 0;
height: unset;
}
}
</style>

View file

@ -0,0 +1,98 @@
<script setup lang="ts">
defineProps<{
data?: {
name: string;
buyer: string;
buyDate: string;
company: string;
approver: string;
approveDate: string;
};
}>();
</script>
<template>
<div class="footer-container">
<div class="footer-top">
<div>ในนาม {{ data?.name || '-' }}</div>
<div>ในนาม {{ data?.company || '-' }}</div>
</div>
<img src="/images/jws-stamp.png" alt="${0}" />
<div class="footer-bottom">
<section>
<div>
<span class="data-placeholder"></span>
<span>งซอสนค</span>
</div>
<div>
<span class="data-placeholder"></span>
<span>นท</span>
</div>
</section>
<section>
<div>
<span class="data-placeholder"></span>
<span>อน</span>
</div>
<div>
<span class="data-placeholder"></span>
<span>นท</span>
</div>
</section>
</div>
</div>
</template>
<style scoped>
.footer-container {
display: flex;
position: relative;
justify-content: center;
height: 1.5in;
}
.footer-top {
position: absolute;
width: 100;
top: 0;
left: 0;
right: 0;
padding: 1rem;
display: flex;
justify-content: space-between;
& > * {
width: 38%;
}
}
.footer-bottom {
position: absolute;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: space-between;
& > * {
display: flex;
width: 38%;
justify-content: space-around;
& > * {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
}
}
.data-placeholder {
display: block;
min-width: 1.2in;
border-bottom: 1px dotted black;
margin-bottom: 2mm;
}
</style>

View file

@ -0,0 +1,192 @@
<script lang="ts" setup>
import { dateFormat } from 'src/utils/datetime';
// NOTE: Import stores
import { formatAddress } from 'src/utils/address';
// NOTE Import Types
import { Branch } from 'src/stores/branch/types';
import { CustomerBranchRelation, Details } from 'src/stores/quotations/types';
// NOTE: Import Components
enum View {
DebitNote,
Invoice,
Payment,
Receipt,
}
defineProps<{
branch: Branch;
customer: CustomerBranchRelation;
details: Details;
view: View;
}>();
function titleMode(mode: View): string {
if (mode === View.DebitNote) {
return 'preview.title.debitNote';
}
if (mode === View.Invoice) {
return 'preview.title.invoice';
}
if (mode === View.Payment) {
return 'preview.title.payment';
}
if (mode === View.Receipt) {
return 'preview.title.receipt';
}
return '';
}
</script>
<template>
<div class="row items-center q-mb-lg">
<div class="column" style="width: 50%">
<img src="/logo.png" width="192px" style="object-fit: scale-down" />
</div>
<div
class="column"
style="text-align: center; width: 50%; font-weight: 800; font-size: 24px"
>
{{ $t(titleMode(view)) }}
</div>
</div>
<article class="detail-card">
<section class="detail-customer-info">
<article>
<b>
{{ !!branch.virtual ? '' : $t('general.company') }} {{ branch.name }}
</b>
<span v-if="branch.province && branch.district && branch.subDistrict">
{{
formatAddress({
address: branch.address,
addressEN: branch.addressEN,
moo: branch.moo,
mooEN: branch.mooEN,
soi: branch.soi,
soiEN: branch.soiEN,
street: branch.street,
streetEN: branch.streetEN,
province: branch.province,
district: branch.district,
subDistrict: branch.subDistrict,
})
}}
</span>
<span>เลขประจำตวผเสยภาษ {{ branch.taxNo }}</span>
<span>เบอรโทร {{ branch.telephoneNo }}</span>
<span>{{ branch.webUrl }}</span>
</article>
<article>
<b>กค</b>
<span>
{{
formatAddress({
address: customer.address,
addressEN: customer.addressEN,
moo: customer.moo,
mooEN: customer.mooEN,
soi: customer.soi,
soiEN: customer.soiEN,
street: customer.street,
streetEN: customer.streetEN,
province: customer.province,
district: customer.district,
subDistrict: customer.subDistrict,
})
}}
</span>
<span>เลขประจำตวผเสยภาษ {{ customer.citizenId }}</span>
<span>เบอรโทร {{ customer.telephoneNo }}</span>
</article>
</section>
<section class="detail-quotation-info">
<div>
<div>{{ $t('general.itemNo', { msg: `${$t(titleMode(view))}` }) }}</div>
<div>{{ details.code }}</div>
</div>
<div>
<div>{{ $t('preview.dateAt', { msg: `${$t(titleMode(view))}` }) }}</div>
<div>{{ dateFormat(details.createdAt, true, false, true) }}</div>
</div>
<div>
<div>{{ $t('preview.seller') }}</div>
<div>{{ details.createdBy }}</div>
</div>
<div>
<div>{{ $t('quotation.paymentCondition') }}</div>
<div>
{{
{
Full: $t('quotation.type.fullAmountCash'),
Split: $t('quotation.type.installmentsCash'),
BillFull: $t('quotation.type.fullAmountBill'),
BillSplit: $t('quotation.type.installmentsBill'),
}[details.payCondition]
}}
</div>
</div>
<div>
<div>{{ $t('quotation.workName') }}</div>
<div>{{ details.workName }}</div>
</div>
<div>
<div>{{ $t('quotation.contactName') }}</div>
<div>{{ details.contactName }}</div>
</div>
<div>
<div>{{ $t('preview.dueDate') }}</div>
<div>{{ dateFormat(details.dueDate, true, false, true) }}</div>
</div>
</section>
</article>
</template>
<style scoped>
.detail-card {
display: flex;
gap: 16px;
& > * {
flex-grow: 1;
}
& > :first-child {
max-width: 57.5%;
}
}
.detail-customer-info {
display: flex;
flex-direction: column;
gap: 16px;
& > * {
display: flex;
flex-direction: column;
& > :first-child {
color: var(--main);
}
}
}
.detail-quotation-info {
& > * {
display: flex;
& > :first-child {
color: var(--main);
}
& > * {
width: 50%;
}
}
}
</style>

View file

@ -0,0 +1,27 @@
<script setup lang="ts">
import { VuePDF, usePDF } from '@tato30/vue-pdf';
const props = defineProps<{
url: string;
}>();
const { pdf, pages } = usePDF(props.url);
</script>
<template>
<section v-for="page in pages" class="content">
<VuePDF style="width: 100%" :pdf="pdf" :page="page" :scale="1.5" />
</section>
</template>
<style scoped>
.content :deep(canvas) {
width: 100% !important;
height: auto !important;
}
@media print {
.content :deep(canvas) {
scale: 1.1;
}
}
</style>