0
0
Fork 0

refactor: refs #6897 refactor vnTable for non input editable table

This commit is contained in:
Pablo Natek 2024-10-23 10:47:44 +02:00
parent 6dd0b32389
commit 54ace8c682
17 changed files with 320 additions and 145 deletions

View File

@ -6,11 +6,11 @@ import useNotify from 'src/composables/useNotify.js';
const { notify } = useNotify();
export default boot(({ app }) => {
app.mixin(qFormMixin);
app.mixin(mainShortcutMixin);
app.directive('shortcut', keyShortcut);
app.config.errorHandler = function (err) {
console.error(err);
notify('globals.error', 'negative', 'error');
};
app.directive('shortcut', keyShortcut);
app.mixin(qFormMixin);
app.mixin(mainShortcutMixin);
});

View File

@ -8,36 +8,27 @@ const props = defineProps({
validator: (value) => value.length <= 3,
},
});
const sectionHeight = computed(() => `${100 / props.colors.length}%`);
const divStyle = computed(() => ({
display: 'flex',
flexDirection: 'column',
width: '100%',
height: '100%',
}));
</script>
<template>
<div class="color-div" :style="divStyle">
<div class="color-div">
<div
v-for="(color, index) in colors"
:key="index"
class="color-section"
:style="{ backgroundColor: color, height: sectionHeight }"
></div>
:style="{
backgroundColor: color,
height: sectionHeight,
width: '100%',
flexShrink: 0,
}"
/>
</div>
</template>
<style scoped>
.color-div {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}
.color-section {
height: 100vh;
width: 100%;
}
</style>

View File

@ -14,6 +14,7 @@ import VnComponent from 'components/common/VnComponent.vue';
import VnUserLink from 'components/ui/VnUserLink.vue';
const model = defineModel(undefined, { required: true });
const emit = defineEmits(['blur']);
const $props = defineProps({
column: {
type: Object,
@ -39,6 +40,10 @@ const $props = defineProps({
type: Object,
default: null,
},
autofocus: {
type: Boolean,
default: false,
},
showLabel: {
type: Boolean,
default: null,
@ -99,6 +104,7 @@ const defaultComponents = {
},
},
checkbox: {
ref: 'checkbox',
component: markRaw(QCheckbox),
attrs: ({ model }) => {
const defaultAttrs = {
@ -115,6 +121,10 @@ const defaultComponents = {
},
forceAttrs: {
label: $props.showLabel && $props.column.label,
autofocus: true,
},
events: {
blur: () => emit('blur'),
},
},
select: {
@ -160,7 +170,27 @@ const col = computed(() => {
return newColumn;
});
const components = computed(() => $props.components ?? defaultComponents);
const components = computed(() => {
const sourceComponents = $props.components ?? defaultComponents;
return Object.keys(sourceComponents).reduce((acc, key) => {
const component = sourceComponents[key];
if (!component || typeof component !== 'object') {
acc[key] = component;
return acc;
}
acc[key] = {
...component,
attrs: {
...(component.attrs || {}),
autofocus: $props.autofocus,
},
};
return acc;
}, {});
});
</script>
<template>
<div class="row no-wrap">
@ -170,6 +200,7 @@ const components = computed(() => $props.components ?? defaultComponents);
:components="components"
:value="{ row, model }"
v-model="model"
@blur="emit('blur')"
/>
<VnComponent
v-if="col.component"
@ -177,6 +208,7 @@ const components = computed(() => $props.components ?? defaultComponents);
:components="components"
:value="{ row, model }"
v-model="model"
@blur="emit('blur')"
/>
<span :title="value" v-else>{{ value }}</span>
<VnComponent
@ -185,6 +217,7 @@ const components = computed(() => $props.components ?? defaultComponents);
:components="components"
:value="{ row, model }"
v-model="model"
@blur="emit('blur')"
/>
</div>
</template>

View File

@ -1,5 +1,5 @@
<script setup>
import { ref, onBeforeMount, onMounted, computed, watch } from 'vue';
import { ref, onBeforeMount, onMounted, computed, watch, nextTick } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import { useQuasar } from 'quasar';
@ -327,6 +327,7 @@ const editingRow = ref(null);
const editingField = ref(null);
const handleClick = (event) => {
console.log('event: ', event);
const clickedElement = event.target.closest('td');
if (!clickedElement) return;
@ -338,17 +339,132 @@ const handleClick = (event) => {
startEditing(Number(rowIndex), colField);
}
};
const startEditing = (rowId, field) => {
const vnEditableCell = ref(null);
const startEditing = async (rowId, field) => {
console.log('entrando a startEditing');
const col = $props.columns.find((col) => col.name === field);
if (col?.isEditable === false) return;
editingRow.value = rowId;
editingField.value = field;
if (col.component === 'checkbox') {
await nextTick();
const inputElement = vnEditableCell.value?.$el?.querySelector('span > div');
inputElement.focus();
}
};
const stopEditing = () => {
const stopEditing = (rowIndex, field) => {
if (editingRow.value !== rowIndex || editingField.value !== field) return;
editingRow.value = null;
editingField.value = null;
};
const findNextEditableColumnIndex = (columns, startIndex) => {
let index = startIndex;
console.log('columns: ', columns);
console.log('index: ', index);
console.log(
'columns[index]?.isVisible === false || columns[index]?.isEditable === false: ',
columns[index]?.isVisible === false || columns[index]?.isEditable === false
);
while (columns[index]?.isVisible === false && columns[index]?.isEditable === false) {
index++;
if (index >= columns.length) {
index = 0;
}
if (index === startIndex) {
return -1;
}
}
console.log('index: ', index);
return index;
};
const handleTab = async (rowIndex, colName) => {
console.log('colName: ', colName);
console.log('rowIndex: ', rowIndex);
const columns = $props.columns;
console.log('columns: ', columns);
if (!Array.isArray(columns) || columns.length === 0) return;
let currentColumnIndex = columns.findIndex((col) => col.name === colName);
if (currentColumnIndex === -1) return;
currentColumnIndex++;
if (currentColumnIndex >= columns.length) {
currentColumnIndex = 0;
rowIndex++;
}
currentColumnIndex = findNextEditableColumnIndex(columns, currentColumnIndex);
if (currentColumnIndex === -1) return;
await startEditing(rowIndex, columns[currentColumnIndex].name);
};
const handleShiftTab = async (rowIndex, colName) => {
console.log('handleShiftTab: ');
const columns = $props.columns;
const currentColumnIndex = columns.findIndex((col) => col.name === colName);
if (currentColumnIndex === -1) return;
let prevColumnIndex = currentColumnIndex - 1;
let prevRowIndex = rowIndex;
while (prevColumnIndex >= 0 && columns[prevColumnIndex]?.isEditable === false) {
prevColumnIndex--;
}
if (prevColumnIndex < 0) {
prevColumnIndex = columns.length - 1;
prevRowIndex -= 1;
while (prevRowIndex >= 0 && columns[prevColumnIndex]?.isEditable === false) {
prevColumnIndex--;
if (prevColumnIndex < 0) {
prevColumnIndex = columns.length - 1;
prevRowIndex--;
}
}
}
if (prevRowIndex < 0) {
stopEditing(rowIndex, colName);
return;
}
await startEditing(prevRowIndex, columns[prevColumnIndex]?.name);
console.log('finishHandleShiftTab');
};
const handleTabKey = async (event, rowIndex, colName) => {
console.log('colName: ', colName);
console.log('rowIndex: ', rowIndex);
console.log('event: ', event);
if (event.shiftKey) await handleShiftTab(rowIndex, colName);
else await handleTab(rowIndex, colName);
};
function getCheckboxIcon(value) {
switch (value) {
case true:
return 'check';
case false:
return 'close';
default:
return 'unknown_med';
}
}
function shouldDisplayReadonly(col, rowIndex) {
return (
col?.isEditable === false ||
editingRow.value !== rowIndex ||
editingField.value !== col?.name
);
}
</script>
<template>
<QDrawer
v-if="$props.rightSearch"
@ -433,7 +549,7 @@ const stopEditing = () => {
<template #body="{ rows }">
<QTable
v-bind="table"
class="vnTable"
:class="['vnTable', table ? 'selection-cell' : '']"
wrap-cells
:columns="splittedColumns.columns"
:rows="rows"
@ -452,10 +568,6 @@ const stopEditing = () => {
"
@row-click="(_, row) => rowClickFunction && rowClickFunction(row)"
@update:selected="emit('update:selected', $event)"
@row-contextmenu="
(event, row, index) =>
console.log('event ', event, ' row ', row, ' index ', index)
"
@click="handleClick"
>
<template #top-left v-if="!$props.withoutHeader">
@ -535,22 +647,27 @@ const stopEditing = () => {
</template>
<template #body-cell="{ col, row, rowIndex }">
<QTd
v-if="col.visible ?? true"
:style="{
maxWidth: col?.width ?? false,
'max-width': col?.width ?? false,
position: 'relative',
}"
class="no-margin body-cell"
:class="[getColAlign(col), col.columnClass]"
v-if="col.visible ?? true"
:class="[
getColAlign(col),
col.columnClass,
'body-cell no-margin no-padding',
]"
:data-row-index="rowIndex"
:data-col-field="col?.name"
:auto-foucs="col?.tabIndex"
>
<div
v-if="
col?.isEditable === false ||
editingRow !== rowIndex ||
editingField !== col?.name
v-if="shouldDisplayReadonly(col, rowIndex)"
class="no-padding no-margin"
style="
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle;
"
>
<slot
@ -561,19 +678,18 @@ const stopEditing = () => {
>
<QIcon
v-if="col?.component === 'checkbox'"
:name="
row[col?.name]
? 'check_box'
: 'check_box_outline_blank' ||
'indeterminate_check_box'
:name="getCheckboxIcon(row[col?.name])"
style="color: var(--vn-text-color)"
size="var(--font-size)"
:class="
isEditable && (col?.isEditable ?? 'editable-text')
"
size="sm"
style="color: var(--vn-label-color)"
:class="col?.isEditable ?? 'editable-text'"
/>
<span
v-else
:class="col?.isEditable ?? 'editable-text'"
:class="
isEditable && (col?.isEditable ?? 'editable-text')
"
:style="col?.style ? col.style(row) : null"
>
{{
@ -584,15 +700,25 @@ const stopEditing = () => {
</span>
</slot>
</div>
<div v-else>
<div v-else-if="isEditable">
<VnTableColumn
ref="vnEditableCell"
:column="col"
:row="row"
:is-editable="col.isEditable ?? isEditable"
v-model="row[col.name]"
component-prop="columnField"
class="cell-input"
auto-focus
class="cell-input q-px-xs"
@blur="stopEditing(rowIndex, col?.name)"
@keyup.enter="stopEditing(rowIndex, col?.name)"
@keydown.tab.prevent="
handleTabKey($event, rowIndex, col?.name)
"
@keydown.shift.tab.prevent="
handleShiftTab(rowIndex, col?.name)
"
@keydown.escape="stopEditing(rowIndex, col?.name)"
:autofocus="true"
/>
</div>
</QTd>
@ -835,22 +961,23 @@ es:
</i18n>
<style lang="scss">
.selection-cell {
table td:first-child {
padding: 0px;
}
}
.side-padding {
padding-left: 10px;
padding-right: 10px;
}
.editable-text:hover {
border: 1px dashed var(--q-primary);
cursor: pointer;
border-bottom: 1px dashed var(--q-primary);
@extend .side-padding;
}
.editable-text {
border-radius: 16px;
box-shadow: 0 0 1px 1px rgb(56, 56, 56);
border: 1px dashed rgba(0, 0, 0, 0.15);
border-bottom: 1px dashed var(--vn-label-color);
@extend .side-padding;
}
.cell-input {
position: absolute;
top: 0;

View File

@ -17,6 +17,8 @@ const $props = defineProps({
},
});
const emit = defineEmits(['blur']);
const componentArray = computed(() => {
if (typeof $props.prop === 'object') return [$props.prop];
return $props.prop;
@ -54,6 +56,7 @@ function toValueAttrs(attrs) {
v-bind="mix(toComponent).attrs"
v-on="mix(toComponent).event ?? {}"
v-model="model"
@blur="emit('blur')"
/>
</span>
</template>

View File

@ -9,6 +9,7 @@ const emit = defineEmits([
'update:options',
'keyup.enter',
'remove',
'blur',
]);
const $props = defineProps({
@ -88,6 +89,7 @@ defineExpose({
:type="$attrs.type"
:class="{ required: $attrs.required }"
@keyup.enter="emit('keyup.enter')"
@blur="emit('blur')"
:clearable="false"
:rules="mixinRules"
:lazy-rules="true"

View File

@ -27,6 +27,7 @@ const isPopupOpen = ref();
const hover = ref();
const mask = ref();
const $attrs = useAttrs();
const emit = defineEmits(['blur']);
const mixinRules = [requiredFieldRule, ...($attrs.rules ?? [])];
@ -102,6 +103,7 @@ const styleAttrs = computed(() => {
:rules="mixinRules"
:clearable="false"
@click="isPopupOpen = true"
@blur="emit('blur')"
hide-bottom-space
>
<template #append>

View File

@ -1,8 +1,9 @@
<script setup>
import VnInput from 'src/components/common/VnInput.vue';
const model = defineModel({ type: [Number, String] });
const emit = defineEmits(['blur']);
</script>
<template>
<VnInput v-bind="$attrs" v-model.number="model" type="number" />
<VnInput v-bind="$attrs" v-model.number="model" type="number" @blur="emit('blur')" />
</template>

View File

@ -24,6 +24,7 @@ const mixinRules = [requiredFieldRule, ...($attrs.rules ?? [])];
const dateFormat = 'HH:mm';
const isPopupOpen = ref();
const hover = ref();
const emit = defineEmits(['blur']);
const styleAttrs = computed(() => {
return props.isOutlined
@ -80,6 +81,7 @@ function dateToTime(newDate) {
style="min-width: 100px"
:rules="mixinRules"
@click="isPopupOpen = false"
@blur="emit('blur')"
type="time"
hide-bottom-space
>

View File

@ -3,7 +3,7 @@ import { ref, toRefs, computed, watch, onMounted, useAttrs } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'src/components/FetchData.vue';
import { useValidator } from 'src/composables/useValidator';
const emit = defineEmits(['update:modelValue', 'update:options', 'remove']);
const emit = defineEmits(['update:modelValue', 'update:options', 'remove', 'blur']);
const $props = defineProps({
modelValue: {
@ -247,6 +247,7 @@ const getVal = (val) => ($props.useLike ? { like: `%${val}%` } : val);
:option-value="optionValue"
v-bind="$attrs"
@filter="filterHandler"
@blur="() => emit('blur')"
:emit-value="nullishToTrue($attrs['emit-value'])"
:map-options="nullishToTrue($attrs['map-options'])"
:use-input="nullishToTrue($attrs['use-input'])"

View File

@ -14,7 +14,7 @@ const $props = defineProps({
},
});
const options = ref([]);
const emit = defineEmits(['blur']);
onBeforeMount(async () => {
const { url, optionValue, optionLabel } = useAttrs();
const findBy = $props.find ?? url?.charAt(0)?.toLocaleLowerCase() + url?.slice(1, -1);
@ -35,5 +35,5 @@ onBeforeMount(async () => {
});
</script>
<template>
<VnSelect v-bind="$attrs" :options="$attrs.options ?? options" />
<VnSelect v-bind="$attrs" :options="$attrs.options ?? options" @blur="emit('blur')" />
</template>

View File

@ -144,11 +144,6 @@ select:-webkit-autofill {
cursor: pointer;
}
.vn-table-separation-row {
height: 16px !important;
background-color: var(--vn-section-color) !important;
}
/* Estilo para el asterisco en campos requeridos */
.q-field.required .q-field__label:after {
content: ' *';
@ -263,7 +258,6 @@ input::-webkit-inner-spin-button {
font-size: 11pt;
}
td {
font-size: 11pt;
border-collapse: collapse;
}
}

View File

@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Checkbox Focus with Button</title>
<style>
body {
font-family: Arial, sans-serif;
}
.checkbox-container {
display: flex;
align-items: center;
margin-bottom: 20px;
}
input[type='checkbox'] {
width: 20px;
height: 20px;
}
label {
margin-left: 10px;
}
/* Estilos para el foco */
input[type='checkbox']:focus {
outline: 2px solid blue;
outline-offset: 2px;
}
</style>
</head>
<body>
<div class="checkbox-container">
<input type="checkbox" id="myCheckbox" />
<label for="myCheckbox">Acepto los términos y condiciones</label>
</div>
<!-- Botón para enfocar el checkbox -->
<button id="focusButton">Dar foco al checkbox</button>
<script>
const checkbox = document.getElementById('myCheckbox');
const focusButton = document.getElementById('focusButton');
// Manejador de eventos para cuando el checkbox recibe el foco
checkbox.addEventListener('focus', () => {
console.log('El checkbox tiene el foco');
});
// Manejador de eventos para cuando el checkbox pierde el foco
checkbox.addEventListener('blur', () => {
console.log('El checkbox perdió el foco');
});
// Manejador de eventos para cuando se cambia el estado del checkbox
checkbox.addEventListener('change', (event) => {
if (event.target.checked) {
console.log('El checkbox está marcado');
} else {
console.log('El checkbox no está marcado');
}
});
// Dar foco al checkbox cuando se presiona el botón
focusButton.addEventListener('click', () => {
checkbox.focus();
});
</script>
</body>
</html>

View File

@ -157,6 +157,7 @@ const onFilterTravelSelected = (formData, id) => {
</VnRow>
<VnRow>
<QCheckbox
v-focus
v-model="data.isOrdered"
:label="t('entry.basicData.ordered')"
/>

View File

@ -9,20 +9,24 @@ import VnTable from 'src/components/VnTable/VnTable.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import FetchedTags from 'components/ui/FetchedTags.vue';
import VnColor from 'src/components/VnColor.vue';
import { dashIfEmpty } from 'src/filters';
const { t } = useI18n();
const selectedRows = ref([]);
const stateStore = useStateStore();
const route = useRoute();
const selectedRows = ref([]);
const columns = [
{
name: 'buyFk',
isId: true,
visible: false,
isEditable: false,
},
{
align: 'center',
label: 'Nv',
name: 'isIgnored',
component: 'checkbox',
width: '35px',
tabIndex: 1,
},
{
align: 'center',
@ -31,12 +35,12 @@ const columns = [
component: 'input',
create: true,
width: '45px',
tabIndex: 2,
},
{
label: '',
name: 'hex',
columnSearch: false,
isEditable: false,
width: '5px',
},
{
@ -61,8 +65,7 @@ const columns = [
label: t('Sti.'),
name: 'stickers',
component: 'number',
width: '50px',
tabIndex: 1,
width: '35px',
},
{
align: 'center',
@ -170,6 +173,7 @@ const columns = [
label: 'Tags',
name: 'tags',
width: '120px',
columnSearch: false,
isEditable: false,
},
{
@ -180,26 +184,26 @@ const columns = [
width: '35px',
},
];
onMounted(() => (stateStore.rightDrawer = false));
onMounted(() => {
stateStore.rightDrawer = false;
});
</script>
<template>
<VnSubToolbar />
<VnTable
ref="tableRef"
data-key="EntryBuys"
:url="`Entries/${route.params.id}/getBuys`"
:disable-option="{ card: true }"
:right-search="false"
:row-click="false"
:columns="columns"
:disable-option="{ card: true }"
class="buyList"
is-editable
auto-load
>
<template #column-hex="{ row }">
{{ console.log('row?.hex: ', row?.hex) }}
<VnColor :colors="['#ff0000']" />
<template #column-hex>
<VnColor :colors="['#ff0000', '#ffff00', '#ff0000']" style="height: 100%" />
</template>
<template #column-name="{ row }">
<span class="link">
@ -211,11 +215,19 @@ onMounted(() => (stateStore.rightDrawer = false));
<FetchedTags :item="row" :columns="3" />
</template>
<template #column-stickers="{ row }">
<span>{{ row.stickers }}</span>
<span style="color: var(--vn-label-color)">/{{ row.printedStickers }}</span>
<span style="color: var(--vn-label-color)">{{ row.printedStickers }}</span>
<span>/{{ row.stickers }}</span>
</template>
</VnTable>
</template>
<style lang="scss" scoped>
.q-checkbox__inner--dark {
&__inner {
border-radius: 0% !important;
background-color: rosybrown;
}
}
</style>
<i18n>
es:
Article: Artículo

View File

@ -271,67 +271,6 @@ const fetchEntryBuys = async () => {
:disable="true"
/>
</QCard>
<!-- <QCard class="vn-two" style="min-width: 100%">
<a class="header header-link">
{{ t('entry.summary.buys') }}
<QIcon name="open_in_new" />
</a>
<QTable
:rows="entryBuys"
:columns="entriesTableColumns"
row-key="index"
class="full-width q-mt-md"
:no-data-label="t('globals.noResults')"
>
<template #body="{ cols, row, rowIndex }">
<QTr no-hover>
<QTd v-for="col in cols" :key="col?.name">
<component
:is="tableColumnComponents[col?.name].component()"
v-bind="tableColumnComponents[col?.name].props()"
@click="tableColumnComponents[col?.name].event()"
class="col-content"
>
<template
v-if="
col?.name !== 'observation' &&
col?.name !== 'isConfirmed'
"
>{{ col.value }}</template
>
<QTooltip v-if="col.toolTip">{{
col.toolTip
}}</QTooltip>
</component>
</QTd>
</QTr>
<QTr no-hover>
<QTd>
<span>{{ row.item.itemType.code }}</span>
</QTd>
<QTd>
<span>{{ row.item.id }}</span>
</QTd>
<QTd>
<span>{{ row.item.size }}</span>
</QTd>
<QTd>
<span>{{ toCurrency(row.item.minPrice) }}</span>
</QTd>
<QTd colspan="6">
<span>{{ row.item.concept }}</span>
<span v-if="row.item.subName" class="subName">
{{ row.item.subName }}
</span>
<FetchedTags :item="row.item" />
</QTd>
</QTr>
<QTr v-if="rowIndex !== entryBuys.length - 1">
<QTd colspan="10" class="vn-table-separation-row" />
</QTr>
</template>
</QTable>
</QCard> -->
</template>
</CardSummary>
</template>

View File

@ -21,9 +21,8 @@ const $props = defineProps({
},
});
</script>
<template>
<QPopupProxy>
<QPopupProxy style="max-width: 10px">
<ItemDescriptor
v-if="$props.id"
:id="$props.id"