generated from corrad-software/corrad-af-2024
283 lines
7.9 KiB
Vue
283 lines
7.9 KiB
Vue
<template>
|
|
<div class="spreadsheet-viewer w-full h-full bg-white dark:bg-gray-900 relative">
|
|
<!-- Toolbar -->
|
|
<div v-if="mode === 'edit'" class="border-b border-gray-200 dark:border-gray-700 p-2 bg-gray-50 dark:bg-gray-800">
|
|
<div class="flex items-center space-x-2">
|
|
<button
|
|
@click="addRow"
|
|
class="px-3 py-1.5 text-sm bg-blue-600 text-white rounded hover:bg-blue-700"
|
|
>
|
|
Add Row
|
|
</button>
|
|
<button
|
|
@click="addColumn"
|
|
class="px-3 py-1.5 text-sm bg-green-600 text-white rounded hover:bg-green-700"
|
|
>
|
|
Add Column
|
|
</button>
|
|
<button
|
|
@click="deleteRow"
|
|
class="px-3 py-1.5 text-sm bg-red-600 text-white rounded hover:bg-red-700"
|
|
:disabled="selectedRow === -1"
|
|
>
|
|
Delete Row
|
|
</button>
|
|
<button
|
|
@click="deleteColumn"
|
|
class="px-3 py-1.5 text-sm bg-red-600 text-white rounded hover:bg-red-700"
|
|
:disabled="selectedColumn === -1"
|
|
>
|
|
Delete Column
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Spreadsheet Grid -->
|
|
<div class="flex-1 overflow-auto">
|
|
<table class="w-full border-collapse">
|
|
<!-- Header Row -->
|
|
<thead>
|
|
<tr>
|
|
<th class="w-12 h-8 bg-gray-100 dark:bg-gray-800 border border-gray-300 dark:border-gray-600"></th>
|
|
<th
|
|
v-for="(col, colIndex) in columnHeaders"
|
|
:key="colIndex"
|
|
@click="selectColumn(colIndex)"
|
|
:class="[
|
|
'h-8 bg-gray-100 dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-center text-sm font-medium cursor-pointer',
|
|
selectedColumn === colIndex ? 'bg-blue-200 dark:bg-blue-800' : ''
|
|
]"
|
|
>
|
|
{{ col }}
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
|
|
<!-- Data Rows -->
|
|
<tbody>
|
|
<tr
|
|
v-for="(row, rowIndex) in spreadsheetData"
|
|
:key="rowIndex"
|
|
>
|
|
<!-- Row Header -->
|
|
<td
|
|
@click="selectRow(rowIndex)"
|
|
:class="[
|
|
'w-12 h-8 bg-gray-100 dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-center text-sm font-medium cursor-pointer',
|
|
selectedRow === rowIndex ? 'bg-blue-200 dark:bg-blue-800' : ''
|
|
]"
|
|
>
|
|
{{ rowIndex + 1 }}
|
|
</td>
|
|
|
|
<!-- Data Cells -->
|
|
<td
|
|
v-for="(cell, colIndex) in row"
|
|
:key="colIndex"
|
|
@click="selectCell(rowIndex, colIndex)"
|
|
:class="[
|
|
'h-8 border border-gray-300 dark:border-gray-600 p-1',
|
|
selectedCell.row === rowIndex && selectedCell.col === colIndex
|
|
? 'bg-blue-100 dark:bg-blue-900 ring-2 ring-blue-500'
|
|
: 'hover:bg-gray-50 dark:hover:bg-gray-800'
|
|
]"
|
|
>
|
|
<input
|
|
v-if="mode === 'edit' && selectedCell.row === rowIndex && selectedCell.col === colIndex"
|
|
v-model="editingValue"
|
|
@blur="finishEdit"
|
|
@keydown.enter="finishEdit"
|
|
@keydown.escape="cancelEdit"
|
|
class="w-full h-full border-none outline-none bg-transparent text-sm"
|
|
ref="cellInput"
|
|
/>
|
|
<span v-else class="text-sm">{{ cell }}</span>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Cell Info Bar -->
|
|
<div class="border-t border-gray-200 dark:border-gray-700 p-2 bg-gray-50 dark:bg-gray-800">
|
|
<div class="flex items-center space-x-4 text-sm">
|
|
<span class="font-medium">
|
|
Cell: {{ getCellReference(selectedCell.row, selectedCell.col) }}
|
|
</span>
|
|
<span v-if="selectedCell.row !== -1 && selectedCell.col !== -1">
|
|
Value: {{ getCurrentCellValue() }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Save Button (Edit Mode) -->
|
|
<div v-if="mode === 'edit'" class="absolute bottom-4 right-4">
|
|
<button
|
|
@click="saveSpreadsheet"
|
|
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 flex items-center space-x-2"
|
|
>
|
|
<Icon name="mdi:content-save" class="w-4 h-4" />
|
|
<span>Save</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, watch, nextTick } from 'vue';
|
|
|
|
const props = defineProps({
|
|
document: {
|
|
type: Object,
|
|
required: true
|
|
},
|
|
mode: {
|
|
type: String,
|
|
default: 'view' // 'view' | 'edit'
|
|
},
|
|
data: {
|
|
type: Array,
|
|
default: () => []
|
|
}
|
|
});
|
|
|
|
const emit = defineEmits([
|
|
'data-changed',
|
|
'save-requested'
|
|
]);
|
|
|
|
// State
|
|
const spreadsheetData = ref([]);
|
|
const selectedCell = ref({ row: -1, col: -1 });
|
|
const selectedRow = ref(-1);
|
|
const selectedColumn = ref(-1);
|
|
const editingValue = ref('');
|
|
const cellInput = ref(null);
|
|
|
|
// Computed
|
|
const columnHeaders = computed(() => {
|
|
const headers = [];
|
|
const maxCols = Math.max(...spreadsheetData.value.map(row => row.length), 0);
|
|
for (let i = 0; i < maxCols; i++) {
|
|
headers.push(String.fromCharCode(65 + i)); // A, B, C, etc.
|
|
}
|
|
return headers;
|
|
});
|
|
|
|
// Methods
|
|
const selectCell = (row, col) => {
|
|
if (mode.value === 'edit') {
|
|
selectedCell.value = { row, col };
|
|
selectedRow.value = -1;
|
|
selectedColumn.value = -1;
|
|
editingValue.value = spreadsheetData.value[row][col] || '';
|
|
|
|
nextTick(() => {
|
|
if (cellInput.value) {
|
|
cellInput.value.focus();
|
|
cellInput.value.select();
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
const selectRow = (row) => {
|
|
selectedRow.value = row;
|
|
selectedCell.value = { row: -1, col: -1 };
|
|
selectedColumn.value = -1;
|
|
};
|
|
|
|
const selectColumn = (col) => {
|
|
selectedColumn.value = col;
|
|
selectedCell.value = { row: -1, col: -1 };
|
|
selectedRow.value = -1;
|
|
};
|
|
|
|
const finishEdit = () => {
|
|
if (selectedCell.value.row !== -1 && selectedCell.value.col !== -1) {
|
|
const newData = [...spreadsheetData.value];
|
|
if (!newData[selectedCell.value.row]) {
|
|
newData[selectedCell.value.row] = [];
|
|
}
|
|
newData[selectedCell.value.row][selectedCell.value.col] = editingValue.value;
|
|
spreadsheetData.value = newData;
|
|
emit('data-changed', newData);
|
|
}
|
|
selectedCell.value = { row: -1, col: -1 };
|
|
};
|
|
|
|
const cancelEdit = () => {
|
|
selectedCell.value = { row: -1, col: -1 };
|
|
editingValue.value = '';
|
|
};
|
|
|
|
const addRow = () => {
|
|
const maxCols = Math.max(...spreadsheetData.value.map(row => row.length), 0);
|
|
const newRow = new Array(maxCols).fill('');
|
|
spreadsheetData.value.push(newRow);
|
|
emit('data-changed', spreadsheetData.value);
|
|
};
|
|
|
|
const addColumn = () => {
|
|
spreadsheetData.value.forEach(row => {
|
|
row.push('');
|
|
});
|
|
emit('data-changed', spreadsheetData.value);
|
|
};
|
|
|
|
const deleteRow = () => {
|
|
if (selectedRow.value !== -1) {
|
|
spreadsheetData.value.splice(selectedRow.value, 1);
|
|
selectedRow.value = -1;
|
|
emit('data-changed', spreadsheetData.value);
|
|
}
|
|
};
|
|
|
|
const deleteColumn = () => {
|
|
if (selectedColumn.value !== -1) {
|
|
spreadsheetData.value.forEach(row => {
|
|
row.splice(selectedColumn.value, 1);
|
|
});
|
|
selectedColumn.value = -1;
|
|
emit('data-changed', spreadsheetData.value);
|
|
}
|
|
};
|
|
|
|
const getCellReference = (row, col) => {
|
|
if (row === -1 || col === -1) return '';
|
|
return `${String.fromCharCode(65 + col)}${row + 1}`;
|
|
};
|
|
|
|
const getCurrentCellValue = () => {
|
|
if (selectedCell.value.row === -1 || selectedCell.value.col === -1) return '';
|
|
return spreadsheetData.value[selectedCell.value.row]?.[selectedCell.value.col] || '';
|
|
};
|
|
|
|
const saveSpreadsheet = () => {
|
|
emit('save-requested');
|
|
};
|
|
|
|
// Watch for data changes
|
|
watch(() => props.data, (newData) => {
|
|
spreadsheetData.value = newData || [];
|
|
}, { immediate: true });
|
|
</script>
|
|
|
|
<style scoped>
|
|
.spreadsheet-viewer {
|
|
font-family: 'Arial', sans-serif;
|
|
}
|
|
|
|
table {
|
|
table-layout: fixed;
|
|
}
|
|
|
|
td, th {
|
|
min-width: 100px;
|
|
max-width: 200px;
|
|
}
|
|
|
|
input {
|
|
font-family: inherit;
|
|
}
|
|
</style> |