235 lines
7.8 KiB
Vue
235 lines
7.8 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed, nextTick } from 'vue'
|
|
import {
|
|
Sheet,
|
|
SheetContent,
|
|
SheetHeader,
|
|
SheetTitle,
|
|
SheetDescription,
|
|
} from '@/components/ui/sheet'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Input } from '@/components/ui/input'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import { Separator } from '@/components/ui/separator'
|
|
import { Pencil, Trash2, Users, Check, X, ChevronLeft } from 'lucide-vue-next'
|
|
import type { SavedView, UpdateSavedViewPayload } from '@/composables/useSavedViews'
|
|
|
|
interface Props {
|
|
open: boolean
|
|
views: SavedView[]
|
|
objectLabel: string
|
|
activeViewId?: string | null
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
activeViewId: null,
|
|
})
|
|
|
|
const emit = defineEmits<{
|
|
'update:open': [value: boolean]
|
|
'apply-view': [view: SavedView]
|
|
'update-view': [id: string, payload: UpdateSavedViewPayload]
|
|
'delete-view': [view: SavedView]
|
|
}>()
|
|
|
|
const editingId = ref<string | null>(null)
|
|
const editName = ref('')
|
|
const deletingId = ref<string | null>(null)
|
|
|
|
// Sharing sub-view: when set, renders RecordSharing for this view
|
|
const sharingView = ref<SavedView | null>(null)
|
|
|
|
const ownViews = computed(() => props.views.filter(v => v.isOwner))
|
|
const sharedViews = computed(() => props.views.filter(v => !v.isOwner))
|
|
|
|
function startEdit(view: SavedView) {
|
|
editingId.value = view.id
|
|
editName.value = view.name
|
|
}
|
|
|
|
function cancelEdit() {
|
|
editingId.value = null
|
|
editName.value = ''
|
|
}
|
|
|
|
function commitEdit(view: SavedView) {
|
|
const name = editName.value.trim()
|
|
if (name && name !== view.name) {
|
|
emit('update-view', view.id, { name })
|
|
}
|
|
cancelEdit()
|
|
}
|
|
|
|
function openSharing(view: SavedView) {
|
|
sharingView.value = view
|
|
}
|
|
|
|
function closeSharing() {
|
|
sharingView.value = null
|
|
}
|
|
|
|
function confirmDelete(view: SavedView) {
|
|
deletingId.value = view.id
|
|
}
|
|
|
|
function cancelDelete() {
|
|
deletingId.value = null
|
|
}
|
|
|
|
function executeDelete(view: SavedView) {
|
|
emit('delete-view', view)
|
|
deletingId.value = null
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<Sheet :open="open" @update:open="emit('update:open', $event)">
|
|
<SheetContent class="w-[420px] sm:w-[520px] overflow-y-auto">
|
|
|
|
<!-- ─── Sharing sub-view ─── -->
|
|
<template v-if="sharingView">
|
|
<SheetHeader class="mb-4">
|
|
<div class="flex items-center gap-2">
|
|
<Button size="icon" variant="ghost" class="h-7 w-7 -ml-1" @click="closeSharing">
|
|
<ChevronLeft class="h-4 w-4" />
|
|
</Button>
|
|
<div>
|
|
<SheetTitle>Share "{{ sharingView.name }}"</SheetTitle>
|
|
<SheetDescription>
|
|
Grant access to specific users for this saved view.
|
|
</SheetDescription>
|
|
</div>
|
|
</div>
|
|
</SheetHeader>
|
|
|
|
<RecordSharing
|
|
object-api-name="SavedListView"
|
|
:record-id="sharingView.id"
|
|
:owner-id="sharingView.userId"
|
|
:base-path="`/saved-views/${sharingView.id}/shares`"
|
|
/>
|
|
</template>
|
|
|
|
<!-- ─── Main view list ─── -->
|
|
<template v-else>
|
|
<SheetHeader class="mb-4">
|
|
<SheetTitle>{{ objectLabel }} — Saved Views</SheetTitle>
|
|
<SheetDescription>
|
|
Manage your saved searches. Share views with specific users from your workspace.
|
|
</SheetDescription>
|
|
</SheetHeader>
|
|
|
|
<!-- Own Views -->
|
|
<section>
|
|
<p class="text-xs font-semibold text-muted-foreground uppercase tracking-wide mb-2">
|
|
My Views
|
|
</p>
|
|
|
|
<div v-if="ownViews.length === 0" class="text-sm text-muted-foreground py-3">
|
|
You have no saved views yet. Run a search and click <strong>Save view</strong>.
|
|
</div>
|
|
|
|
<ul class="space-y-1">
|
|
<li
|
|
v-for="view in ownViews"
|
|
:key="view.id"
|
|
class="group rounded-md border bg-card px-3 py-2"
|
|
>
|
|
<!-- Confirm delete row -->
|
|
<div v-if="deletingId === view.id" class="flex items-center gap-2">
|
|
<span class="flex-1 text-sm text-destructive">Delete "{{ view.name }}"?</span>
|
|
<Button size="sm" variant="destructive" @click="executeDelete(view)">Delete</Button>
|
|
<Button size="sm" variant="outline" @click="cancelDelete">Cancel</Button>
|
|
</div>
|
|
|
|
<!-- Edit name row -->
|
|
<div v-else-if="editingId === view.id" class="flex items-center gap-2">
|
|
<Input
|
|
v-model="editName"
|
|
class="h-7 flex-1 text-sm"
|
|
@keyup.enter="commitEdit(view)"
|
|
@keyup.escape="cancelEdit"
|
|
autofocus
|
|
/>
|
|
<Button size="icon" variant="ghost" class="h-7 w-7" @click="commitEdit(view)">
|
|
<Check class="h-3.5 w-3.5" />
|
|
</Button>
|
|
<Button size="icon" variant="ghost" class="h-7 w-7" @click="cancelEdit">
|
|
<X class="h-3.5 w-3.5" />
|
|
</Button>
|
|
</div>
|
|
|
|
<!-- Normal row -->
|
|
<div v-else class="flex items-center gap-2 min-h-[28px]">
|
|
<!-- View name (click to apply) -->
|
|
<button
|
|
class="flex-1 text-left text-sm truncate hover:text-primary transition-colors"
|
|
:class="{ 'font-medium text-primary': activeViewId === view.id }"
|
|
@click="emit('apply-view', view); emit('update:open', false)"
|
|
>
|
|
{{ view.name }}
|
|
</button>
|
|
|
|
<!-- Actions (visible on hover) -->
|
|
<div class="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
<Button size="icon" variant="ghost" class="h-6 w-6" title="Rename" @click="startEdit(view)">
|
|
<Pencil class="h-3 w-3" />
|
|
</Button>
|
|
<Button
|
|
size="icon"
|
|
variant="ghost"
|
|
class="h-6 w-6"
|
|
title="Share"
|
|
@click="openSharing(view)"
|
|
>
|
|
<Users class="h-3 w-3" />
|
|
</Button>
|
|
<Button size="icon" variant="ghost" class="h-6 w-6 text-destructive hover:text-destructive" title="Delete" @click="confirmDelete(view)">
|
|
<Trash2 class="h-3 w-3" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Description -->
|
|
<p
|
|
v-if="view.description && editingId !== view.id && deletingId !== view.id"
|
|
class="text-xs text-muted-foreground mt-1 truncate"
|
|
>
|
|
{{ view.description }}
|
|
</p>
|
|
</li>
|
|
</ul>
|
|
</section>
|
|
|
|
<!-- Shared views by others -->
|
|
<template v-if="sharedViews.length > 0">
|
|
<Separator class="my-4" />
|
|
<section>
|
|
<p class="text-xs font-semibold text-muted-foreground uppercase tracking-wide mb-2">
|
|
Shared with me
|
|
</p>
|
|
<ul class="space-y-1">
|
|
<li
|
|
v-for="view in sharedViews"
|
|
:key="view.id"
|
|
class="rounded-md border bg-card px-3 py-2"
|
|
>
|
|
<button
|
|
class="w-full text-left text-sm truncate hover:text-primary transition-colors"
|
|
:class="{ 'font-medium text-primary': activeViewId === view.id }"
|
|
@click="emit('apply-view', view); emit('update:open', false)"
|
|
>
|
|
{{ view.name }}
|
|
</button>
|
|
<p v-if="view.description" class="text-xs text-muted-foreground mt-1 truncate">
|
|
{{ view.description }}
|
|
</p>
|
|
</li>
|
|
</ul>
|
|
</section>
|
|
</template>
|
|
</template>
|
|
</SheetContent>
|
|
</Sheet>
|
|
</template>
|