ваше сообщение коммита
This commit is contained in:
@@ -93,6 +93,49 @@ router.post('/:id/rows', async (req, res, next) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Получить строки таблицы с фильтрацией по продукту и тегам
|
||||
router.get('/:id/rows', async (req, res, next) => {
|
||||
try {
|
||||
const tableId = req.params.id;
|
||||
const { product, tags } = req.query; // tags = "B2B,VIP"
|
||||
// Получаем все столбцы, строки и значения ячеек
|
||||
const columns = (await db.getQuery()('SELECT * FROM user_columns WHERE table_id = $1', [tableId])).rows;
|
||||
const rows = (await db.getQuery()('SELECT * FROM user_rows WHERE table_id = $1', [tableId])).rows;
|
||||
const cellValues = (await db.getQuery()('SELECT * FROM user_cell_values WHERE row_id IN (SELECT id FROM user_rows WHERE table_id = $1)', [tableId])).rows;
|
||||
|
||||
// Находим id нужных колонок
|
||||
const productCol = columns.find(c => c.options && c.options.purpose === 'product');
|
||||
const tagsCol = columns.find(c => c.options && c.options.purpose === 'userTags');
|
||||
|
||||
// Собираем строки с нужными полями
|
||||
const data = rows.map(row => {
|
||||
const cells = cellValues.filter(cell => cell.row_id === row.id);
|
||||
return {
|
||||
id: row.id,
|
||||
product: cells.find(c => c.column_id === productCol?.id)?.value,
|
||||
userTags: cells.find(c => c.column_id === tagsCol?.id)?.value,
|
||||
// ... другие поля при необходимости
|
||||
};
|
||||
});
|
||||
|
||||
// Фильтрация на сервере
|
||||
let filtered = data;
|
||||
if (product) {
|
||||
filtered = filtered.filter(r => r.product === product);
|
||||
}
|
||||
if (tags) {
|
||||
const tagArr = tags.split(',').map(t => t.trim());
|
||||
filtered = filtered.filter(r =>
|
||||
tagArr.some(tag => (r.userTags || '').split(',').map(t => t.trim()).includes(tag))
|
||||
);
|
||||
}
|
||||
|
||||
res.json(filtered);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
// Изменить значение ячейки (доступно всем)
|
||||
router.patch('/cell/:cellId', async (req, res, next) => {
|
||||
try {
|
||||
|
||||
@@ -9,6 +9,23 @@
|
||||
{{ rebuildStatus.message }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Фильтры на Element Plus -->
|
||||
<div class="table-filters-el" v-if="productOptions.length || tagOptions.length">
|
||||
<el-select v-model="selectedProduct" placeholder="Все продукты" clearable style="min-width: 180px;">
|
||||
<el-option v-for="opt in productOptions" :key="opt" :label="opt" :value="opt" />
|
||||
</el-select>
|
||||
<el-select
|
||||
v-model="selectedTags"
|
||||
multiple
|
||||
filterable
|
||||
collapse-tags
|
||||
placeholder="Теги"
|
||||
style="min-width: 220px;"
|
||||
>
|
||||
<el-option v-for="tag in tagOptions" :key="tag" :label="tag" :value="tag" />
|
||||
</el-select>
|
||||
<el-button @click="resetFilters" type="default" icon="el-icon-refresh">Сбросить фильтры</el-button>
|
||||
</div>
|
||||
<div class="notion-table-wrapper">
|
||||
<table class="notion-table">
|
||||
<thead>
|
||||
@@ -30,7 +47,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in rows" :key="row.id">
|
||||
<tr v-for="row in filteredRows" :key="row.id">
|
||||
<td v-for="col in columns" :key="col.id">
|
||||
<TableCell
|
||||
:rowId="row.id"
|
||||
@@ -100,11 +117,13 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ref, onMounted, computed, watch } from 'vue';
|
||||
import tablesService from '../../services/tablesService';
|
||||
import TableCell from './TableCell.vue';
|
||||
import { useAuthContext } from '@/composables/useAuth';
|
||||
import axios from 'axios';
|
||||
// Импортируем компоненты Element Plus
|
||||
import { ElSelect, ElOption, ElButton } from 'element-plus';
|
||||
const { isAdmin } = useAuthContext();
|
||||
const rebuilding = ref(false);
|
||||
const rebuildStatus = ref(null);
|
||||
@@ -115,6 +134,13 @@ const rows = ref([]);
|
||||
const cellValues = ref([]);
|
||||
const tableMeta = ref(null);
|
||||
|
||||
// Фильтры
|
||||
const selectedProduct = ref('');
|
||||
const selectedTags = ref([]);
|
||||
const productOptions = ref([]);
|
||||
const tagOptions = ref([]);
|
||||
const filteredRows = ref([]);
|
||||
|
||||
// Для модалки добавления столбца
|
||||
const showAddColModal = ref(false);
|
||||
const newColName = ref('');
|
||||
@@ -161,6 +187,53 @@ async function loadTags() {
|
||||
tags.value = await res.json();
|
||||
}
|
||||
|
||||
// Получение уникальных значений для фильтров
|
||||
function updateFilterOptions() {
|
||||
// product
|
||||
const productCol = columns.value.find(c => c.options && c.options.purpose === 'product');
|
||||
const tagCol = columns.value.find(c => c.options && c.options.purpose === 'userTags');
|
||||
const products = new Set();
|
||||
const tagsSet = new Set();
|
||||
rows.value.forEach(row => {
|
||||
const cells = cellValues.value.filter(cell => cell.row_id === row.id);
|
||||
const prod = cells.find(c => c.column_id === productCol?.id)?.value;
|
||||
if (prod) products.add(prod);
|
||||
const tagsVal = cells.find(c => c.column_id === tagCol?.id)?.value;
|
||||
if (tagsVal) tagsVal.split(',').map(t => t.trim()).forEach(t => t && tagsSet.add(t));
|
||||
});
|
||||
productOptions.value = Array.from(products);
|
||||
tagOptions.value = Array.from(tagsSet);
|
||||
}
|
||||
|
||||
// Загрузка данных с фильтрацией
|
||||
async function fetchFilteredRows() {
|
||||
const data = await tablesService.getFilteredRows(props.tableId, {
|
||||
product: selectedProduct.value,
|
||||
tags: selectedTags.value
|
||||
});
|
||||
filteredRows.value = data;
|
||||
}
|
||||
|
||||
// Основная загрузка таблицы
|
||||
async function fetchTable() {
|
||||
const data = await tablesService.getTable(props.tableId);
|
||||
columns.value = data.columns;
|
||||
rows.value = data.rows;
|
||||
cellValues.value = data.cellValues;
|
||||
tableMeta.value = { name: data.name, description: data.description };
|
||||
updateFilterOptions();
|
||||
await fetchFilteredRows();
|
||||
}
|
||||
|
||||
// Сброс фильтров
|
||||
function resetFilters() {
|
||||
selectedProduct.value = '';
|
||||
selectedTags.value = [];
|
||||
fetchFilteredRows();
|
||||
}
|
||||
|
||||
watch([selectedProduct, selectedTags], fetchFilteredRows);
|
||||
|
||||
onMounted(() => {
|
||||
fetchTable();
|
||||
loadTags();
|
||||
@@ -240,15 +313,6 @@ function startChangeTypeCol(col) {
|
||||
alert('Изменение типа столбца пока не реализовано');
|
||||
}
|
||||
|
||||
// Загрузка данных
|
||||
async function fetchTable() {
|
||||
const data = await tablesService.getTable(props.tableId);
|
||||
columns.value = data.columns;
|
||||
rows.value = data.rows;
|
||||
cellValues.value = data.cellValues;
|
||||
tableMeta.value = { name: data.name, description: data.description };
|
||||
}
|
||||
|
||||
function saveCellValue(rowId, columnId, value) {
|
||||
tablesService.saveCell({ row_id: rowId, column_id: columnId, value }).then(fetchTable);
|
||||
}
|
||||
@@ -501,4 +565,10 @@ tr:hover .delete-row-btn {
|
||||
.rebuild-status.error {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
.table-filters-el {
|
||||
display: flex;
|
||||
gap: 1.2em;
|
||||
align-items: center;
|
||||
margin-bottom: 1.2em;
|
||||
}
|
||||
</style>
|
||||
@@ -53,5 +53,12 @@ export default {
|
||||
console.error('Error in deleteTable service:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
async getFilteredRows(tableId, { product = '', tags = [] } = {}) {
|
||||
const params = new URLSearchParams();
|
||||
if (product) params.append('product', product);
|
||||
if (tags.length) params.append('tags', tags.join(','));
|
||||
const res = await api.get(`/tables/${tableId}/rows?${params.toString()}`);
|
||||
return res.data;
|
||||
}
|
||||
};
|
||||
@@ -49,5 +49,10 @@ export default defineConfig({
|
||||
rewrite: (path) => path,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
usePolling: true,
|
||||
interval: 1000,
|
||||
ignored: ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/.idea/**', '**/.vscode/**']
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user