refactor: enhance access control and visibility logic across various components
Some checks failed
Spell Check / Spell Check with Typos (push) Failing after 6s

This commit is contained in:
puriphatt 2025-07-07 12:42:52 +07:00
parent a59e0c5157
commit c481266654
8 changed files with 43 additions and 30 deletions

View file

@ -48,6 +48,7 @@ defineProps<{
readonly?: boolean; readonly?: boolean;
onDrawer?: boolean; onDrawer?: boolean;
inputOnly?: boolean; inputOnly?: boolean;
disableToggle?: boolean;
}>(); }>();
defineEmits<{ defineEmits<{
@ -76,6 +77,7 @@ defineEmits<{
<ToggleButton <ToggleButton
class="q-mr-sm" class="q-mr-sm"
two-way two-way
:disable="disableToggle"
:model-value="status !== 'INACTIVE'" :model-value="status !== 'INACTIVE'"
@click=" @click="
() => { () => {
@ -195,8 +197,8 @@ defineEmits<{
} }
:deep( :deep(
.q-item__section.column.q-item__section--side.justify-center.q-item__section--avatar.q-focusable.relative-position.cursor-pointer .q-item__section.column.q-item__section--side.justify-center.q-item__section--avatar.q-focusable.relative-position.cursor-pointer
) { ) {
justify-content: start !important; justify-content: start !important;
padding-right: 8px !important; padding-right: 8px !important;
padding-top: 16px; padding-top: 16px;
@ -208,15 +210,15 @@ defineEmits<{
} }
:deep( :deep(
i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated i.q-icon.mdi.mdi-chevron-down-circle.q-expansion-item__toggle-icon.q-expansion-item__toggle-icon--rotated
) { ) {
color: var(--brand-1); color: var(--brand-1);
} }
:deep( :deep(
.q-item.q-item-type.row.no-wrap.q-item--dense.q-item--clickable.q-link.cursor-pointer.q-focusable.q-hoverable.expansion-rounded.surface-2 .q-item.q-item-type.row.no-wrap.q-item--dense.q-item--clickable.q-link.cursor-pointer.q-focusable.q-hoverable.expansion-rounded.surface-2
.q-focus-helper .q-focus-helper
) { ) {
visibility: hidden; visibility: hidden;
} }

View file

@ -1,7 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { BranchWithChildren } from 'stores/branch/types'; import { BranchWithChildren } from 'stores/branch/types';
import KebabAction from './shared/KebabAction.vue'; import KebabAction from './shared/KebabAction.vue';
import { isRoleInclude } from 'stores/utils';
const nodes = defineModel<(any | BranchWithChildren)[]>('nodes', { const nodes = defineModel<(any | BranchWithChildren)[]>('nodes', {
default: [], default: [],
@ -120,17 +119,7 @@ defineEmits<{
/> />
<q-btn <q-btn
v-if=" v-if="node.isHeadOffice && typeTree === 'branch'"
node.isHeadOffice &&
typeTree === 'branch' &&
isRoleInclude([
'system',
'head_of_admin',
'admin',
'executive',
'accountant',
])
"
:id="`create-sub-branch-btn-${node.name}`" :id="`create-sub-branch-btn-${node.name}`"
@click.stop="$emit('create', node)" @click.stop="$emit('create', node)"
icon="mdi-file-plus-outline" icon="mdi-file-plus-outline"

View file

@ -14,7 +14,7 @@ import { FloatingActionButton, PaginationComponent } from 'src/components';
import PaginationPageSize from 'src/components/PaginationPageSize.vue'; import PaginationPageSize from 'src/components/PaginationPageSize.vue';
import PropertyDialog from './PropertyDialog.vue'; import PropertyDialog from './PropertyDialog.vue';
import { Property } from 'src/stores/property/types'; import { Property } from 'src/stores/property/types';
import { dialog, toCamelCase } from 'src/stores/utils'; import { dialog, toCamelCase, canAccess } from 'src/stores/utils';
import CreateButton from 'src/components/AddButton.vue'; import CreateButton from 'src/components/AddButton.vue';
import useOptionStore from 'stores/options'; import useOptionStore from 'stores/options';
import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue'; import AdvanceSearch from 'src/components/shared/AdvanceSearch.vue';
@ -331,6 +331,7 @@ watch(
</script> </script>
<template> <template>
<FloatingActionButton <FloatingActionButton
v-if="canAccess('workflow', 'edit')"
style="z-index: 999" style="z-index: 999"
hide-icon hide-icon
@click="triggerDialog('add')" @click="triggerDialog('add')"
@ -536,11 +537,19 @@ watch(
class="col surface-2 flex items-center justify-center" class="col surface-2 flex items-center justify-center"
> >
<NoData <NoData
v-if="pageState.total !== 0 || pageState.searchDate.length > 0" v-if="
pageState.total !== 0 ||
pageState.searchDate.length > 0 ||
!canAccess('workflow', 'edit')
"
:not-found="!!pageState.inputSearch" :not-found="!!pageState.inputSearch"
/> />
<CreateButton <CreateButton
v-if="pageState.total === 0 && pageState.searchDate.length === 0" v-if="
pageState.total === 0 &&
pageState.searchDate.length === 0 &&
canAccess('workflow', 'edit')
"
@click="triggerDialog('add')" @click="triggerDialog('add')"
label="general.add" label="general.add"
:i18n-args="{ text: $t('flow.title') }" :i18n-args="{ text: $t('flow.title') }"
@ -698,6 +707,7 @@ watch(
" "
/> />
<KebabAction <KebabAction
v-if="canAccess('workflow', 'edit')"
:id-name="props.row.name" :id-name="props.row.name"
:status="props.row.status" :status="props.row.status"
@view=" @view="
@ -815,6 +825,7 @@ watch(
" "
/> />
<KebabAction <KebabAction
v-if="canAccess('workflow', 'edit')"
:id-name="props.row.id" :id-name="props.row.id"
:status="props.row.status" :status="props.row.status"
@view=" @view="
@ -906,6 +917,7 @@ watch(
@drawer-undo="() => undo()" @drawer-undo="() => undo()"
@close="() => resetForm()" @close="() => resetForm()"
@submit="() => submit()" @submit="() => submit()"
:hide-action="!canAccess('workflow', 'edit')"
:readonly="!pageState.isDrawerEdit" :readonly="!pageState.isDrawerEdit"
:isEdit="pageState.isDrawerEdit" :isEdit="pageState.isDrawerEdit"
v-model="pageState.addModal" v-model="pageState.addModal"

View file

@ -30,6 +30,7 @@ withDefaults(
defineProps<{ defineProps<{
readonly?: boolean; readonly?: boolean;
isEdit?: boolean; isEdit?: boolean;
hideAction?: boolean;
}>(), }>(),
{ readonly: false, isEdit: false }, { readonly: false, isEdit: false },
); );
@ -151,7 +152,7 @@ defineEmits<{
style="position: absolute; z-index: 999; top: 0; right: 0" style="position: absolute; z-index: 999; top: 0; right: 0"
> >
<div <div
v-if="propertyData.status !== 'INACTIVE'" v-if="propertyData.status !== 'INACTIVE' && !hideAction"
class="surface-1 row rounded" class="surface-1 row rounded"
> >
<UndoButton <UndoButton
@ -236,6 +237,7 @@ defineEmits<{
<FormProperty <FormProperty
onDrawer onDrawer
:readonly="!isEdit" :readonly="!isEdit"
:disable-toggle="hideAction"
v-model:name="formProperty.name" v-model:name="formProperty.name"
v-model:name-en="formProperty.nameEN" v-model:name-en="formProperty.nameEN"
v-model:type="formProperty.type" v-model:type="formProperty.type"

View file

@ -480,7 +480,7 @@ onMounted(async () => {
</div> </div>
<!-- bill --> <!-- bill -->
<span class="app-text-muted-2 q-pt-md"> <span class="app-text-muted-2 q-pt-md" v-if="paymentData.length > 0">
{{ $t('quotation.receiptDialog.billOfPayment') }} {{ $t('quotation.receiptDialog.billOfPayment') }}
</span> </span>

View file

@ -9,6 +9,7 @@ import {
dialogWarningClose, dialogWarningClose,
formatNumberDecimal, formatNumberDecimal,
canAccess, canAccess,
isRoleInclude,
} from 'stores/utils'; } from 'stores/utils';
import { ProductTree, quotationProductTree } from './utils'; import { ProductTree, quotationProductTree } from './utils';
@ -1736,7 +1737,7 @@ function covertToNode() {
:readonly=" :readonly="
{ {
quotation: quotationFormState.mode !== 'edit', quotation: quotationFormState.mode !== 'edit',
invoice: false, invoice: isRoleInclude(['sale', 'head_of_sale']),
accepted: true, accepted: true,
}[view] }[view]
" "
@ -1940,6 +1941,7 @@ function covertToNode() {
view !== View.Receipt && view !== View.Receipt &&
view !== View.Complete view !== View.Complete
" "
:readonly="isRoleInclude(['sale', 'head_of_sale'])"
:data="quotationFormState.source" :data="quotationFormState.source"
v-model:first-code-payment="firstCodePayment" v-model:first-code-payment="firstCodePayment"
@fetch-status=" @fetch-status="

View file

@ -34,7 +34,7 @@ import {
import { RequestWork } from 'src/stores/request-list/types'; import { RequestWork } from 'src/stores/request-list/types';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import useOptionStore from 'src/stores/options'; import useOptionStore from 'src/stores/options';
import { dialogWarningClose, canAccess } from 'src/stores/utils'; import { dialogWarningClose, canAccess, isRoleInclude } from 'src/stores/utils';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { QForm } from 'quasar'; import { QForm } from 'quasar';
import { getName } from 'src/services/keycloak'; import { getName } from 'src/services/keycloak';
@ -728,7 +728,7 @@ onMounted(async () => {
<AdditionalFileExpansion <AdditionalFileExpansion
v-if="view !== CreditNoteStatus.Success" v-if="view !== CreditNoteStatus.Success"
:readonly="false" :readonly="isRoleInclude(['sale', 'head_of_sale'])"
v-model:file-data="attachmentData" v-model:file-data="attachmentData"
:transform-url=" :transform-url="
async (url: string) => { async (url: string) => {

View file

@ -46,7 +46,13 @@ import {
import { RequestWork } from 'src/stores/request-list/types'; import { RequestWork } from 'src/stores/request-list/types';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import useOptionStore from 'src/stores/options'; import useOptionStore from 'src/stores/options';
import { deleteItem, dialog, dialogWarningClose } from 'src/stores/utils'; import {
canAccess,
deleteItem,
dialog,
dialogWarningClose,
isRoleInclude,
} from 'src/stores/utils';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { Employee } from 'src/stores/employee/types'; import { Employee } from 'src/stores/employee/types';
import QuotationFormWorkerSelect from '../05_quotation/QuotationFormWorkerSelect.vue'; import QuotationFormWorkerSelect from '../05_quotation/QuotationFormWorkerSelect.vue';
@ -1071,6 +1077,7 @@ async function submitAccepted() {
<PaymentForm <PaymentForm
v-if="view === QuotationStatus.PaymentPending" v-if="view === QuotationStatus.PaymentPending"
is-debit-note is-debit-note
:readonly="isRoleInclude(['sale', 'head_of_sale'])"
:data="debitNoteData" :data="debitNoteData"
@fetch-status=" @fetch-status="
() => { () => {
@ -1126,7 +1133,6 @@ async function submitAccepted() {
view === QuotationStatus.Issued || view === QuotationStatus.Issued ||
view === QuotationStatus.Accepted view === QuotationStatus.Accepted
" "
readonly
:total-price="summaryPrice.finalPrice" :total-price="summaryPrice.finalPrice"
class="q-mb-md" class="q-mb-md"
v-model:pay-type="currentFormData.payCondition" v-model:pay-type="currentFormData.payCondition"
@ -1144,7 +1150,7 @@ async function submitAccepted() {
view === QuotationStatus.Accepted || view === QuotationStatus.Accepted ||
view === QuotationStatus.PaymentPending view === QuotationStatus.PaymentPending
" "
:readonly :readonly="isRoleInclude(['sale', 'head_of_sale'])"
v-model:file-data="attachmentData" v-model:file-data="attachmentData"
:transform-url=" :transform-url="
async (url: string) => { async (url: string) => {