Update Person grid view vue and reverted import v2 back to v1 (v2 not production ready)
This commit is contained in:
@@ -2,9 +2,12 @@
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/Components/ui/dialog";
|
||||
import { Button } from "@/Components/ui/button";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/Components/ui/select";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/Components/ui/table";
|
||||
import { Badge } from "@/Components/ui/badge";
|
||||
import { Label } from "@/Components/ui/label";
|
||||
import { Checkbox } from "@/Components/ui/checkbox";
|
||||
import { ChevronRightIcon } from "@heroicons/vue/24/outline";
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
show: Boolean,
|
||||
limit: Number,
|
||||
@@ -14,72 +17,187 @@ const props = defineProps({
|
||||
truncated: Boolean,
|
||||
hasHeader: Boolean,
|
||||
})
|
||||
|
||||
const emits = defineEmits(['close','change-limit','refresh'])
|
||||
function onLimit(e){ emits('change-limit', Number(e.target.value)); emits('refresh') }
|
||||
|
||||
// State
|
||||
const selectedRow = ref(null);
|
||||
const hideEmptyRows = ref(true);
|
||||
|
||||
// Filter out columns with empty headers
|
||||
const visibleColumns = computed(() => {
|
||||
if (!props.columns) return [];
|
||||
return props.columns.filter(col => col && String(col).trim() !== '');
|
||||
});
|
||||
|
||||
// Check if row is empty (first 2 columns are empty)
|
||||
function isRowEmpty(row) {
|
||||
if (!visibleColumns.value || visibleColumns.value.length === 0) return false;
|
||||
const firstCols = visibleColumns.value.slice(0, 2);
|
||||
return firstCols.every(col => !row[col] || String(row[col]).trim() === '');
|
||||
}
|
||||
|
||||
// Filtered rows
|
||||
const visibleRows = computed(() => {
|
||||
if (!props.rows) return [];
|
||||
let filtered = props.rows;
|
||||
if (hideEmptyRows.value) {
|
||||
filtered = filtered.filter(r => !isRowEmpty(r));
|
||||
}
|
||||
return filtered.map((r, idx) => ({ ...r, index: idx + 1 }));
|
||||
});
|
||||
|
||||
// Select row
|
||||
function selectRow(row) {
|
||||
selectedRow.value = row;
|
||||
}
|
||||
|
||||
function onLimit(val) {
|
||||
emits('change-limit', Number(val));
|
||||
emits('refresh');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog :open="show" @update:open="(val) => !val && $emit('close')">
|
||||
<DialogContent class="max-w-6xl max-h-[90vh] overflow-hidden flex flex-col">
|
||||
<DialogHeader>
|
||||
<DialogTitle>CSV Preview ({{ rows.length }} / {{ limit }})</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div class="flex items-center gap-3 pb-3 border-b">
|
||||
<div class="flex items-center gap-2">
|
||||
<Label for="limit-select" class="text-sm text-gray-600">Limit:</Label>
|
||||
<Select :model-value="String(limit)" @update:model-value="(val) => { emits('change-limit', Number(val)); emits('refresh'); }">
|
||||
<SelectTrigger id="limit-select" class="w-24 h-8">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="50">50</SelectItem>
|
||||
<SelectItem value="100">100</SelectItem>
|
||||
<SelectItem value="200">200</SelectItem>
|
||||
<SelectItem value="300">300</SelectItem>
|
||||
<SelectItem value="500">500</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<DialogContent class="max-w-7xl max-h-[90vh] overflow-hidden flex flex-col p-0">
|
||||
<!-- Header -->
|
||||
<div class="px-6 py-4 border-b bg-linear-to-r from-gray-50 to-white">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-gray-900">CSV Preview</h2>
|
||||
<p class="text-sm text-gray-500 mt-1">
|
||||
Showing {{ visibleRows.length }} of {{ rows.length }} rows
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<Label for="limit-select" class="text-sm text-gray-600">Limit:</Label>
|
||||
<Select :model-value="String(limit)" @update:model-value="onLimit">
|
||||
<SelectTrigger id="limit-select" class="w-24 h-8">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="50">50</SelectItem>
|
||||
<SelectItem value="100">100</SelectItem>
|
||||
<SelectItem value="200">200</SelectItem>
|
||||
<SelectItem value="300">300</SelectItem>
|
||||
<SelectItem value="500">500</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<Button @click="$emit('refresh')" variant="outline" size="sm" :disabled="loading">
|
||||
{{ loading ? 'Loading…' : 'Refresh' }}
|
||||
</Button>
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="hide-empty-rows"
|
||||
:checked="hideEmptyRows"
|
||||
@update:checked="(val) => hideEmptyRows = val"
|
||||
/>
|
||||
<Label for="hide-empty-rows" class="text-xs cursor-pointer">
|
||||
Hide empty rows
|
||||
</Label>
|
||||
</div>
|
||||
<Badge v-if="truncated" variant="outline" class="bg-amber-50 text-amber-700 border-amber-200">
|
||||
Truncated at limit
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<Button @click="$emit('refresh')" variant="outline" size="sm" :disabled="loading">
|
||||
{{ loading ? 'Loading…' : 'Refresh' }}
|
||||
</Button>
|
||||
<Badge v-if="truncated" variant="outline" class="bg-amber-50 text-amber-700 border-amber-200">
|
||||
Truncated at limit
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-auto border rounded-lg">
|
||||
<Table>
|
||||
<TableHeader class="sticky top-0 bg-white z-10">
|
||||
<TableRow>
|
||||
<TableHead class="w-16">#</TableHead>
|
||||
<TableHead v-for="col in columns" :key="col">{{ col }}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow v-if="loading">
|
||||
<TableCell :colspan="columns.length + 1" class="text-center text-gray-500">
|
||||
Loading…
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow v-for="(r, idx) in rows" :key="idx">
|
||||
<TableCell class="text-gray-500 font-medium">{{ idx + 1 }}</TableCell>
|
||||
<TableCell v-for="col in columns" :key="col" class="whitespace-pre-wrap">
|
||||
{{ r[col] }}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow v-if="!loading && !rows.length">
|
||||
<TableCell :colspan="columns.length + 1" class="text-center text-gray-500">
|
||||
No rows
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
<!-- Split View -->
|
||||
<div class="flex-1 flex overflow-hidden">
|
||||
<!-- Left Panel - Row List -->
|
||||
<div class="w-96 border-r bg-gray-50 overflow-y-auto">
|
||||
<div v-if="loading" class="p-8 text-center text-gray-500">
|
||||
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-2"></div>
|
||||
Loading...
|
||||
</div>
|
||||
<div v-else-if="!visibleRows.length" class="p-8 text-center text-gray-500">
|
||||
No rows to display
|
||||
</div>
|
||||
<div v-else class="divide-y">
|
||||
<button
|
||||
v-for="row in visibleRows"
|
||||
:key="row.index"
|
||||
@click="selectRow(row)"
|
||||
class="w-full px-4 py-3 text-left hover:bg-white transition-colors"
|
||||
:class="{
|
||||
'bg-white shadow-sm': selectedRow?.index === row.index,
|
||||
}"
|
||||
>
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-3 flex-1 min-w-0">
|
||||
<!-- Row Number -->
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-8 h-8 rounded-full bg-blue-100 text-blue-700 flex items-center justify-center text-xs font-semibold">
|
||||
{{ row.index }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Row Preview -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-xs font-semibold text-gray-900 mb-1">
|
||||
Row #{{ row.index }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-600 truncate">
|
||||
{{
|
||||
visibleColumns.slice(0, 2).map(col => row[col]).filter(Boolean).join(' • ') || 'Empty row'
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Arrow -->
|
||||
<ChevronRightIcon class="h-4 w-4 text-gray-400 flex-shrink-0" />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Panel - Row Details -->
|
||||
<div v-if="selectedRow" class="flex-1 overflow-y-auto p-6">
|
||||
<!-- Row Header -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-lg font-semibold text-gray-900">
|
||||
Row #{{ selectedRow.index }}
|
||||
</h3>
|
||||
<p class="text-sm text-gray-500">Full row details</p>
|
||||
</div>
|
||||
|
||||
<!-- Row Data -->
|
||||
<div class="bg-gray-50 rounded-lg p-4">
|
||||
<dl class="grid grid-cols-1 gap-3">
|
||||
<div
|
||||
v-for="col in visibleColumns"
|
||||
:key="col"
|
||||
class="flex items-start gap-3 py-2 border-b border-gray-200 last:border-0"
|
||||
>
|
||||
<dt class="text-sm font-medium text-gray-600 w-48 flex-shrink-0">
|
||||
{{ col }}
|
||||
</dt>
|
||||
<dd class="text-sm text-gray-900 flex-1 font-medium whitespace-pre-wrap break-words">
|
||||
{{ selectedRow[col] || '—' }}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Empty State for Right Panel -->
|
||||
<div v-else class="flex-1 flex items-center justify-center text-gray-400">
|
||||
<div class="text-center">
|
||||
<div class="text-5xl mb-3">📄</div>
|
||||
<p class="text-sm">Select a row to view details</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-gray-500 pt-3 border-t">
|
||||
Showing up to {{ limit }} rows from source file.
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="px-6 py-3 border-t bg-gray-50 text-xs text-gray-500">
|
||||
Header detection: <span class="font-medium">{{ hasHeader ? 'header present' : 'no header' }}</span>
|
||||
• Click a row to view full details
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
Reference in New Issue
Block a user