refactor: responsive (#180)

* refactor: can open one dropdown whe lt.md

* style: update MainLayout background color and fix avatar border class name

* feat: add touch position binding for dropdown in ProfileMenu

* refactor: enhance icon styling in DrawerComponent

* fix: update screen size conditions

* feat: add responsive search and filter functionality in MainPage

* feat: update styling and functionality in BasicInformation and MainPage components

* feat: package view mode improve layout and responsiveness

* feat: improve layout and responsiveness of ProfileBanner component

* feat: enhance TreeView component with improved icon layout and cursor pointer styling

* feat: update DialogForm component to prevent text wrapping in the center column

* feat: enhance FormDocument, PriceDataComponent, and BasicInfoProduct components with layout and styling improvements

* feat: enhance ProfileBanner dark tab

* feat: 02 => responsive & responsibleArea type

* fix: layout header bg condition & 02 filter col

* feat: 04 flow => add AddButton component and enhance layout in FormFlow and FlowDialog

* feat: 07 => enhance layout and responsiveness

* refactor: simplify header structure and improve layout consistency

* fix: improve text color in ItemCard and adjust responsive breakpoints in product service group

* refactor: 05 => enhance layout responsiveness and improve class bindings in quotation components

* refactor: enhance styling and improve props flexibility in dialog and select components

* refactor: 05 => enhance layout responsiveness in quotation components

* refactor: 05 => enhance layout responsiveness

* refactor: 05 => formWorkerAdd

* refactor: 05 => formWorkerAdd Product table

* refactor: 05 => improve layout responsiveness and enhance component structure

* refactor: enhance grid view handling and improve component imports

* refactor: improve column classes for better layout consistency

* refactor: 09 => enhance layout structure and improve responsiveness in task order views

* refactor: 10 => enhance invoice main page layout and improve component interactions

* refactor: 13 => enhance receipt main page layout and improve component interactions

* refactor: 11 => enhance layout and improve responsiveness in credit note pages

* refactor: 01 => screen.sm search & filter

* refactor: 01 => improve layout responsiveness and fix variable naming in branch management forms

---------

Co-authored-by: puriphatt <puriphat@frappet.com>
This commit is contained in:
Methapon Metanipat 2025-01-27 10:39:53 +07:00 committed by GitHub
parent 79ec995547
commit e0c1725001
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 993 additions and 609 deletions

View file

@ -44,7 +44,7 @@ defineEmits<{
}>();
const bankBookOptions = ref<Record<string, unknown>[]>([]);
let bankBoookFilter: (
let bankBookFilter: (
value: string,
update: (callbackFn: () => void, afterFn?: (ref: QSelect) => void) => void,
) => void;
@ -77,7 +77,7 @@ function change(e: Event) {
onMounted(() => {
if (optionStore.globalOption) {
bankBoookFilter = selectFilterOptionRefMod(
bankBookFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption.bankBook),
bankBookOptions,
'label',
@ -94,7 +94,7 @@ onMounted(() => {
watch(
() => optionStore.globalOption,
() => {
bankBoookFilter = selectFilterOptionRefMod(
bankBookFilter = selectFilterOptionRefMod(
ref(optionStore.globalOption.bankBook),
bankBookOptions,
'label',
@ -131,8 +131,8 @@ watch(
<div
v-for="(book, i) in bankBookList"
class="col-12 row"
:class="{ 'q-pt-lg': i !== 0 }"
class="col-12"
:class="{ 'q-pt-lg': i !== 0, row: $q.screen.gt.sm }"
:key="i"
>
<q-separator
@ -172,8 +172,8 @@ watch(
</span>
<div
class="bordered q-mr-sm rounded"
:class="{ 'pointer-none': readonly }"
class="bordered q-mr-sm rounded col text-center overflow-hidden"
:class="{ 'pointer-none': readonly, 'q-my-sm': $q.screen.lt.md }"
>
<ImageHover
:readonly="readonly"
@ -214,7 +214,7 @@ watch(
@update:model-value="
(v) => (typeof v === 'string' ? (book.bankName = v) : '')
"
@filter="bankBoookFilter"
@filter="bankBookFilter"
@clear="book.bankName = ''"
>
<template v-slot:option="scope">

View file

@ -67,7 +67,7 @@ defineProps<{
outlined
:readonly="readonly"
hide-bottom-space
class="col-6 col-md-4"
class="col-12 col-md-4"
:label="$t('form.telephone')"
for="input-telephone-no"
:model-value="readonly ? telephoneNo || '-' : telephoneNo"
@ -116,7 +116,7 @@ defineProps<{
outlined
:readonly="readonly"
hide-bottom-space
class="col-6 col-md-4"
class="col-12 col-md-4"
:label="$t('branch.form.contactTelephone')"
for="input-contact"
:model-value="readonly ? contact || '-' : contact"
@ -139,7 +139,7 @@ defineProps<{
outlined
:readonly="readonly"
hide-bottom-space
class="col-6 col-md-4"
class="col-12 col-md-4"
:label="$t('branch.form.webUrl')"
for="input-web-url"
:model-value="readonly ? webUrl || '-' : webUrl"

View file

@ -203,7 +203,7 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
outlined
:readonly="readonly"
hide-bottom-space
class="col-4"
class="col-md-4 col-12"
:label="$t('general.licenseNumber')"
v-model="permitNo"
:rules="[(val) => val && val.length > 0]"
@ -212,7 +212,7 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
/>
<DatePicker
class="col-3"
class="col-md-3 col-12"
id="input-start-date"
:readonly="readonly"
:label="$t('general.dateOfIssue')"
@ -221,7 +221,7 @@ function formatCode(input: string | undefined, type: 'code' | 'number') {
/>
<DatePicker
class="col-3"
class="col-md-3 col-12"
id="input-start-date"
:readonly="readonly"
:label="$t('general.expirationDate')"

View file

@ -19,6 +19,7 @@ import SelectMenuWithSearch from '../shared/SelectMenuWithSearch.vue';
import ToggleButton from 'src/components/button/ToggleButton.vue';
import NoData from '../NoData.vue';
import SelectBranch from '../shared/select/SelectBranch.vue';
import AddButton from '../button/AddButton.vue';
defineProps<{
readonly?: boolean;
@ -127,6 +128,7 @@ function optionSearch(val: string | null) {
defineEmits<{
(e: 'moveUp'): void;
(e: 'addStep'): void;
(e: 'moveDown'): void;
(e: 'changeStatus'): void;
(e: 'triggerProperties', step: WorkFlowPayloadStep): void;
@ -159,7 +161,10 @@ onMounted(async () => {
style="background-color: var(--surface-3)"
/>
{{ $t(`general.name`, { msg: $t('flow.title') }) }}
<span class="row q-ml-lg items-center text-weight-regular text-body2">
<span
class="row items-center text-weight-regular text-body2"
:class="{ 'q-ml-lg': $q.screen.gt.xs, 'q-mt-sm': $q.screen.lt.sm }"
>
<ToggleButton
class="q-mr-sm"
two-way
@ -216,6 +221,13 @@ onMounted(async () => {
style="background-color: var(--surface-3)"
/>
{{ $t(`flow.processStep`) }}
<AddButton
v-if="!readonly && $q.screen.lt.md"
id="btn-add-work"
icon-only
class="q-ml-sm"
@click="$emit('addStep')"
/>
</section>
<section

View file

@ -229,7 +229,7 @@ const detailEditorImageDrop = createEditorImageDrop(detail);
"
@drop="detailEditorImageDrop"
min-height="5rem"
class="q-mt-sm q-mb-xs"
class="q-mt-sm q-mb-xs col"
:flat="!readonly"
:readonly="readonly"
:toolbar-color="
@ -275,4 +275,8 @@ const detailEditorImageDrop = createEditorImageDrop(detail);
:deep(.q-editor__toolbar) {
border-color: var(--surface-3) !important;
}
:deep(.q-editor.q-editor--default) {
width: 1px;
}
</style>

View file

@ -168,7 +168,7 @@ const detailEditorImageDrop = createEditorImageDrop(detail);
/>
<q-field
class="full-width"
class="col-12"
outlined
for="input-service-description"
id="input-service-description"
@ -180,31 +180,34 @@ const detailEditorImageDrop = createEditorImageDrop(detail);
>
<q-editor
dense
class="q-mt-sm q-mb-xs col"
:model-value="
readonly ? serviceDescription || '-' : serviceDescription || ''
"
@update:model-value="
(v) => (typeof v === 'string' ? (serviceDescription = v) : '')
"
@drop="detailEditorImageDrop"
min-height="5rem"
class="q-mt-sm q-mb-xs"
:flat="!readonly"
:readonly="readonly"
:toolbar-toggle-color="readonly ? 'disabled' : 'primary'"
:toolbar-color="
readonly ? 'disabled' : $q.dark.isActive ? 'white' : ''
"
:toolbar-toggle-color="readonly ? 'disabled' : 'primary'"
style="
cursor: auto;
color: var(--foreground);
border-color: var(--surface-3);
"
:style="`width: ${$q.screen.gt.xs ? '100%' : '63vw'}`"
@update:model-value="
(v) => (typeof v === 'string' ? (serviceDescription = v) : '')
"
@drop="detailEditorImageDrop"
/>
</q-field>
</div>
</div>
</template>
<style scoped></style>
<style scoped>
:deep(.q-editor.q-editor--default) {
width: 1px;
}
</style>

View file

@ -106,6 +106,7 @@ function optionSearch(val: string | null) {
<div class="col-12 row q-col-gutter-sm">
<q-select
behavior="menu"
:readonly
outlined
dense

View file

@ -164,7 +164,7 @@ withDefaults(
</div>
</div>
<div class="col-12">
<div class="col-12 full-width">
<q-table
:columns="column"
:rows="row"

View file

@ -38,7 +38,7 @@ defineEmits<{
style="background-color: var(--surface-3)"
/>
{{ $t(`general.about`) }}
<div class="q-ml-md text-weight-regular">
<div class="text-weight-regular" :class="{ 'q-ml-md ': $q.screen.gt.sm }">
<q-checkbox
:label="$t('productService.product.agentPrice')"
size="xs"

View file

@ -1,9 +1,7 @@
<script setup lang="ts">
import { Icon } from '@iconify/vue/dist/iconify.js';
import { formatNumberDecimal } from 'src/stores/utils';
import BadgeComponent from 'components/BadgeComponent.vue';
import KebabAction from '../shared/KebabAction.vue';
import MainButton from '../button/MainButton.vue';
defineProps<{
title?: string;
@ -48,7 +46,7 @@ const rand = Math.random();
</script>
<template>
<div
class="surface-1 rounded q-pa-sm quo-card bordered"
class="surface-1 rounded q-pa-sm quo-card bordered column"
:class="{ 'urgent-card': urgent }"
:style="{ '--animation-delay': rand + 's' }"
>
@ -71,7 +69,7 @@ const rand = Math.random();
/>
</div>
<nav class="col text-right">
<nav class="col text-right no-wrap">
<q-btn
v-if="!hidePreview"
flat
@ -125,7 +123,7 @@ const rand = Math.random();
<!-- SEC: body -->
<section
class="rounded q-px-sm"
class="rounded q-px-sm col"
:class="{
'surface-1': urgent,
'surface-2': !urgent,

View file

@ -35,6 +35,7 @@ type Options = { label: string; value: string };
<div class="col-12 row q-col-gutter-sm">
<SelectInput
:class="{ col: $q.screen.lt.md }"
:disable="!readonly && onDrawer"
:readonly="readonly"
for="input-agencies-code"
@ -60,7 +61,7 @@ type Options = { label: string; value: string };
outlined
:readonly="readonly"
hide-bottom-space
class="col"
class="col-md col-12"
:label="$t('agencies.name')"
v-model="name"
:rules="[(val: string) => !!val || $t('form.error.required')]"
@ -71,7 +72,7 @@ type Options = { label: string; value: string };
outlined
:readonly="readonly"
hide-bottom-space
class="col"
class="col-md col-12"
:label="'Agencies Name'"
v-model="nameEn"
/>

View file

@ -202,7 +202,7 @@ const currentTab = defineModel<string>('currentTab');
</div>
<!-- center -->
<div class="col column full-height">
<div class="col column full-height no-wrap">
<slot></slot>
</div>

View file

@ -42,7 +42,9 @@ onMounted(() => {
:class="{ dark: $q.dark.isActive }"
:style="`background-color: ${bgColor}`"
>
<div style="font-size: 14px">{{ $t(text) }}</div>
<div style="font-size: 14px; color: var(--foreground)">
{{ $t(text) }}
</div>
</div>
</div>
</button>

View file

@ -154,15 +154,15 @@ const smallBanner = ref(false);
class="absolute-bottom-right"
style="
border-radius: 50%;
width: 20px;
height: 20px;
width: 1.25vw;
height: 1.25vw;
z-index: 2;
background: var(--surface-1);
"
>
<q-badge
class="absolute-center"
style="border-radius: 50%; width: 14px; height: 14px"
style="border-radius: 50%; width: 0.8vw; height: 0.8vw"
:style="`background: hsl(var(${active ? '--positive-bg' : '--text-mute'}))`"
></q-badge>
</q-badge>
@ -193,7 +193,7 @@ const smallBanner = ref(false);
>
<div
class="row justify-between full-height"
style="padding-left: 7.5vw"
style="padding-left: calc(6vw + 50px)"
>
<div class="col column">
<span
@ -261,7 +261,7 @@ const smallBanner = ref(false);
inline-label
mobile-arrows
v-model="currentTab"
active-class="active-tab text-weight-bold"
:active-class="`active-tab text-weight-bold ${$q.dark.isActive && 'dark'}`"
class="app-text-muted full-width"
align="left"
v-if="typeof tabsList === 'object'"
@ -271,6 +271,7 @@ const smallBanner = ref(false);
:id="`${prefix}-tab-${tab.label}`"
v-bind:key="tab.name"
class="content-tab text-capitalize"
:class="{ 'tab-label': currentTab !== tab.name }"
:name="tab.name"
:label="tab.label"
/>
@ -302,7 +303,11 @@ const smallBanner = ref(false);
>
<!-- profile -->
<span class="row col items-center">
<div class="flex items-center full-height q-pl-lg" style="z-index: 1">
<div
class="flex items-center full-height"
:class="{ 'q-pl-lg': $q.screen.gt.sm, 'q-pl-sm': $q.screen.lt.md }"
style="z-index: 1"
>
<div
class="surface-1"
style="border-radius: 50%; border: 2px solid var(--surface-1)"
@ -347,13 +352,14 @@ const smallBanner = ref(false);
<Icon
class="full-width full-height flex items-center justify-center"
:icon="icon || 'mdi-account'"
style="width: 25px !important"
/>
</div>
</template>
</q-img>
<div
v-else
class="full-width full-height flex items-center justify-center"
class="full-width full-height flex items-center justify-center no-padding"
:style="{
background: `${bgColor || 'var(--brand-1)'}`,
color: `${color || 'white'}`,
@ -362,6 +368,7 @@ const smallBanner = ref(false);
<Icon
class="full-width full-height flex items-center justify-center"
:icon="icon || 'mdi-account'"
style="width: 25px !important"
/>
</div>
</template>
@ -378,6 +385,7 @@ const smallBanner = ref(false);
<Icon
class="full-width full-height flex items-center justify-center"
:icon="icon || 'mdi-account'"
style="width: 25px !important"
/>
</div>
@ -425,7 +433,7 @@ const smallBanner = ref(false);
inline-label
mobile-arrows
v-model="currentTab"
active-class="active-tab text-weight-bold"
:active-class="`active-tab text-weight-bold ${$q.dark.isActive && 'dark'}`"
class="app-text-muted full-width"
align="left"
v-if="typeof tabsList === 'object'"
@ -435,6 +443,7 @@ const smallBanner = ref(false);
:id="`${prefix}-tab-${tab.label}`"
v-bind:key="tab.name"
class="content-tab text-capitalize"
:class="{ 'tab-label': currentTab !== tab.name }"
:name="tab.name"
:label="tab.label"
/>
@ -526,5 +535,13 @@ const smallBanner = ref(false);
.active-tab {
color: var(--brand-1);
&.dark {
filter: brightness(1.3);
}
}
.tab-label {
color: var(--foreground);
opacity: 75%;
}
</style>

View file

@ -57,13 +57,13 @@ async function downloadImage(url: string | null) {
<main class="column full-height">
<section
v-if="!hideTab"
style="background: var(--gray-3)"
:style="`background: var(${$q.dark.isActive ? '--gray-8' : '--gray-3'})`"
class="q-py-sm row justify-center"
>
<div class="surface-2 q-px-md q-py-sm rounded row no-wrap items-center">
<MainButton
icon="mdi-minus"
color="0 0% 0%"
:color="`var(--gray-${$q.dark.isActive ? '1' : '11'}-hsl)`"
icon-only
@click="
() => {
@ -90,7 +90,7 @@ async function downloadImage(url: string | null) {
></q-input>
<MainButton
icon="mdi-plus"
color="0 0% 0%"
:color="`var(--gray-${$q.dark.isActive ? '1' : '11'}-hsl)`"
icon-only
@click="
() => {

View file

@ -16,8 +16,8 @@ const props = withDefaults(
id?: string;
label?: string;
option: T[];
optionLabel?: keyof T;
optionValue?: keyof T;
optionLabel?: keyof T | string;
optionValue?: keyof T | string;
placeholder?: string;
hideSelected?: boolean;

View file

@ -74,16 +74,16 @@ function assignSelect(to: unknown[], from: unknown[]) {
<template>
<section class="full-width column">
<header
class="row items-center no-wrap q-px-md q-py-sm"
class="row items-center q-px-md q-py-sm"
:class="{ 'bordered surface-3 ': borderSearchSection }"
>
<div class="col"><slot name="top"></slot></div>
<div class="col-12 col-md"><slot name="top"></slot></div>
<q-input
for="input-search"
outlined
dense
:label="$t('general.search')"
class="q-ml-auto"
class="col-12 col-md-3"
:bg-color="$q.dark.isActive ? 'dark' : 'white'"
v-model="search"
debounce="300"

View file

@ -118,6 +118,9 @@ function visibleNode(text: string, node: Node, ancestor?: Node[]): boolean {
<template v-for="(node, i) in nodes" :key="i">
<div
class="tree-item"
:class="{
'cursor-pointer': node.children ? true : undefined,
}"
v-if="filterText ? visibleNode(filterText, node, ancestorNode) : true"
>
<slot
@ -205,19 +208,21 @@ function visibleNode(text: string, node: Node, ancestor?: Node[]): boolean {
/>
</label>
<div
class="item__icon flex items-center justify-center"
:style="`background: ${node.bg || dec?.bg}; color: ${node.fg || dec?.fg}; height: ${iconSize}; width: ${iconSize}`"
>
<div>
<div
class="flex items-center justify-center"
:style="`height: calc(${iconSize} - 40%); width: calc(${iconSize} - 40%)`"
class="item__icon flex items-center justify-center"
:style="`background: ${node.bg || dec?.bg}; color: ${node.fg || dec?.fg}; height: ${iconSize}; width: ${iconSize}`"
>
<Icon
v-if="(node.icon && dec && dec.icon) || (dec && dec.icon)"
:icon="node.icon || dec.icon"
class="full-width full-height"
/>
<div
class="flex items-center justify-center"
:style="`height: calc(${iconSize} - 40%); width: calc(${iconSize} - 40%)`"
>
<Icon
v-if="(node.icon && dec && dec.icon) || (dec && dec.icon)"
:icon="node.icon || dec.icon"
class="full-width full-height"
/>
</div>
</div>
</div>