421 lines
20 KiB
HTML
421 lines
20 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Shot Delete Debug Test</title>
|
|
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<style>
|
|
.table-row {
|
|
cursor: pointer;
|
|
transition: background-color 0.15s;
|
|
}
|
|
.table-row:hover {
|
|
background-color: #f9fafb;
|
|
}
|
|
.table-row.detail-opened {
|
|
background-color: #dbeafe;
|
|
}
|
|
.dropdown-menu {
|
|
display: none;
|
|
position: absolute;
|
|
background: white;
|
|
border: 1px solid #e5e7eb;
|
|
border-radius: 6px;
|
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
|
z-index: 50;
|
|
min-width: 160px;
|
|
}
|
|
.dropdown-menu.show {
|
|
display: block;
|
|
}
|
|
.dropdown-item {
|
|
padding: 8px 12px;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 14px;
|
|
}
|
|
.dropdown-item:hover {
|
|
background-color: #f3f4f6;
|
|
}
|
|
.dropdown-item.destructive {
|
|
color: #dc2626;
|
|
}
|
|
.dropdown-item.destructive:hover {
|
|
background-color: #fef2f2;
|
|
}
|
|
.icon {
|
|
width: 16px;
|
|
height: 16px;
|
|
stroke: currentColor;
|
|
fill: none;
|
|
stroke-width: 2;
|
|
}
|
|
.log {
|
|
background: #f8fafc;
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 6px;
|
|
padding: 12px;
|
|
font-family: monospace;
|
|
font-size: 12px;
|
|
max-height: 300px;
|
|
overflow-y: auto;
|
|
}
|
|
.detail-panel {
|
|
position: fixed;
|
|
top: 0;
|
|
right: 0;
|
|
width: 400px;
|
|
height: 100vh;
|
|
background: white;
|
|
border-left: 1px solid #e5e7eb;
|
|
box-shadow: -4px 0 6px -1px rgba(0, 0, 0, 0.1);
|
|
transform: translateX(100%);
|
|
transition: transform 0.3s ease;
|
|
z-index: 100;
|
|
}
|
|
.detail-panel.open {
|
|
transform: translateX(0);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="bg-gray-50 p-8">
|
|
<div id="app">
|
|
<div class="max-w-6xl mx-auto space-y-6">
|
|
<h1 class="text-2xl font-bold mb-6">Shot Delete Debug Test</h1>
|
|
|
|
<!-- Event Log -->
|
|
<div class="bg-white rounded-lg border p-4">
|
|
<h2 class="text-lg font-semibold mb-3">Event Log</h2>
|
|
<div class="log" id="eventLog">
|
|
<div class="text-gray-500">Events will be logged here...</div>
|
|
</div>
|
|
<button @click="clearLog" class="mt-2 px-3 py-1 bg-gray-500 text-white rounded text-sm hover:bg-gray-600">
|
|
Clear Log
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Test Table -->
|
|
<div class="bg-white rounded-lg border overflow-hidden">
|
|
<h2 class="text-lg font-semibold p-4 border-b">Shot Table - Debug Test</h2>
|
|
|
|
<table class="w-full">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-4 py-3 text-left text-sm font-medium text-gray-900">Shot Name</th>
|
|
<th class="px-4 py-3 text-left text-sm font-medium text-gray-900">Status</th>
|
|
<th class="px-4 py-3 text-left text-sm font-medium text-gray-900">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr
|
|
v-for="shot in shots"
|
|
:key="shot.id"
|
|
class="table-row border-b"
|
|
:class="{ 'detail-opened': shot.id === selectedShotId }"
|
|
@click="handleRowClick(shot, $event)"
|
|
>
|
|
<td class="px-4 py-3">
|
|
<div class="font-medium">{{ shot.name }}</div>
|
|
</td>
|
|
<td class="px-4 py-3">
|
|
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs bg-green-100 text-green-800">
|
|
{{ shot.status }}
|
|
</span>
|
|
</td>
|
|
<td class="px-4 py-3 relative">
|
|
<div class="relative">
|
|
<button
|
|
@click="toggleDropdown(shot.id, $event)"
|
|
@mousedown="preventBubbling"
|
|
class="inline-flex items-center justify-center w-8 h-8 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded"
|
|
:data-shot-id="shot.id"
|
|
data-radix-dropdown-menu-trigger
|
|
>
|
|
<svg class="icon" viewBox="0 0 24 24">
|
|
<circle cx="12" cy="12" r="1"></circle>
|
|
<circle cx="19" cy="12" r="1"></circle>
|
|
<circle cx="5" cy="12" r="1"></circle>
|
|
</svg>
|
|
</button>
|
|
|
|
<div
|
|
v-if="shot.dropdownOpen"
|
|
class="dropdown-menu show"
|
|
style="top: 100%; right: 0;"
|
|
@click="preventBubbling"
|
|
@mousedown="preventBubbling"
|
|
data-radix-dropdown-menu-content
|
|
>
|
|
<div
|
|
class="dropdown-item"
|
|
@click="editShot(shot, $event)"
|
|
@mousedown="preventBubbling"
|
|
data-radix-dropdown-menu-item
|
|
>
|
|
<svg class="icon" viewBox="0 0 24 24">
|
|
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
|
|
<path d="m18.5 2.5 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
|
|
</svg>
|
|
Edit Shot
|
|
</div>
|
|
<div
|
|
class="dropdown-item"
|
|
@click="viewTasks(shot, $event)"
|
|
@mousedown="preventBubbling"
|
|
data-radix-dropdown-menu-item
|
|
>
|
|
<svg class="icon" viewBox="0 0 24 24">
|
|
<path d="M3 6h18"></path>
|
|
<path d="M3 12h18"></path>
|
|
<path d="M3 18h18"></path>
|
|
</svg>
|
|
View Tasks
|
|
</div>
|
|
<hr class="my-1 border-gray-200">
|
|
<div
|
|
class="dropdown-item destructive"
|
|
@click="deleteShot(shot, $event)"
|
|
@mousedown="preventBubbling"
|
|
data-radix-dropdown-menu-item
|
|
>
|
|
<svg class="icon" viewBox="0 0 24 24">
|
|
<polyline points="3,6 5,6 21,6"></polyline>
|
|
<path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
|
|
</svg>
|
|
Delete Shot
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Detail Panel -->
|
|
<div class="detail-panel" :class="{ 'open': selectedShotId !== null }">
|
|
<div class="p-4 border-b">
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-lg font-semibold">Shot Detail Panel</h3>
|
|
<button @click="closeDetailPanel" class="text-gray-400 hover:text-gray-600">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="p-4" v-if="selectedShot">
|
|
<h4 class="font-medium">{{ selectedShot.name }}</h4>
|
|
<p class="text-sm text-gray-600 mt-2">Status: {{ selectedShot.status }}</p>
|
|
<p class="text-sm text-gray-600 mt-1">This panel should NOT open when clicking delete!</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Test Instructions -->
|
|
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
|
<h3 class="font-semibold text-blue-900 mb-2">Debug Instructions</h3>
|
|
<ul class="text-blue-800 text-sm space-y-1">
|
|
<li>• <strong>Row Click:</strong> Click on shot name or status - should open detail panel</li>
|
|
<li>• <strong>Dropdown Button:</strong> Click three-dot menu - should NOT open detail panel</li>
|
|
<li>• <strong>Edit/View Tasks:</strong> Click menu items - should NOT open detail panel</li>
|
|
<li>• <strong>Delete Shot:</strong> Click "Delete Shot" - should NOT open detail panel (main issue)</li>
|
|
<li>• Watch the event log to see what events are being triggered</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const { createApp } = Vue
|
|
|
|
createApp({
|
|
data() {
|
|
return {
|
|
shots: [
|
|
{ id: 1, name: 'SH001_Opening', status: 'In Progress', dropdownOpen: false },
|
|
{ id: 2, name: 'SH002_Chase', status: 'Not Started', dropdownOpen: false },
|
|
{ id: 3, name: 'SH003_Dialog', status: 'Completed', dropdownOpen: false }
|
|
],
|
|
selectedShotId: null,
|
|
selectedShot: null
|
|
}
|
|
},
|
|
methods: {
|
|
handleRowClick(shot, event) {
|
|
// Simulate the enhanced handleRowClick logic from ShotsDataTable.vue
|
|
const target = event.target
|
|
if (!target) {
|
|
this.openDetailPanel(shot)
|
|
return
|
|
}
|
|
|
|
// First, check if we clicked on any interactive element using closest()
|
|
const interactiveSelectors = [
|
|
'button',
|
|
'[role="button"]',
|
|
'[role="menuitem"]',
|
|
'[role="menu"]',
|
|
'[data-radix-collection-item]',
|
|
'[data-radix-dropdown-menu-trigger]',
|
|
'[data-radix-dropdown-menu-content]',
|
|
'[data-radix-dropdown-menu-item]',
|
|
'[data-state]',
|
|
'[data-radix-popper-content-wrapper]',
|
|
'.dropdown-menu',
|
|
'.dropdown-trigger',
|
|
'input',
|
|
'select',
|
|
'textarea',
|
|
'a[href]',
|
|
'[tabindex]:not([tabindex="-1"])',
|
|
'svg',
|
|
'.lucide',
|
|
'.icon'
|
|
]
|
|
|
|
const interactiveElement = target.closest(interactiveSelectors.join(', '))
|
|
if (interactiveElement) {
|
|
this.logEvent(`🚫 Row click prevented by closest() - found: ${interactiveElement.tagName || 'element'} with ${interactiveElement.getAttribute('data-radix-dropdown-menu-item') ? 'data-radix-dropdown-menu-item' : 'other attributes'}`)
|
|
return
|
|
}
|
|
|
|
// Second, traverse up the DOM tree to check each parent element
|
|
let currentElement = target
|
|
while (currentElement && currentElement !== event.currentTarget) {
|
|
// Check various conditions that indicate this is an interactive element
|
|
if (
|
|
// Has onclick handler
|
|
currentElement.onclick ||
|
|
// Is a button element
|
|
currentElement.tagName === 'BUTTON' ||
|
|
// Has button role
|
|
currentElement.getAttribute('role') === 'button' ||
|
|
currentElement.getAttribute('role') === 'menuitem' ||
|
|
// Has Radix UI attributes
|
|
currentElement.hasAttribute('data-radix-dropdown-menu-trigger') ||
|
|
currentElement.hasAttribute('data-radix-dropdown-menu-content') ||
|
|
currentElement.hasAttribute('data-radix-dropdown-menu-item') ||
|
|
currentElement.hasAttribute('data-radix-collection-item') ||
|
|
// Has cursor-pointer class but is not the row itself
|
|
(currentElement.classList.contains('cursor-pointer') && currentElement !== event.currentTarget) ||
|
|
// Is an SVG or icon
|
|
currentElement.tagName === 'svg' ||
|
|
currentElement.classList.contains('lucide') ||
|
|
currentElement.classList.contains('icon') ||
|
|
// Has specific dropdown classes
|
|
currentElement.classList.contains('dropdown-menu') ||
|
|
currentElement.classList.contains('dropdown-trigger')
|
|
) {
|
|
this.logEvent(`🚫 Row click prevented by traversal - found: ${currentElement.tagName} with ${currentElement.getAttribute('data-radix-dropdown-menu-item') ? 'data-radix-dropdown-menu-item' : 'other attributes'}`)
|
|
return
|
|
}
|
|
|
|
currentElement = currentElement.parentElement
|
|
}
|
|
|
|
// If we get here, it's safe to open the detail panel
|
|
this.openDetailPanel(shot)
|
|
},
|
|
|
|
openDetailPanel(shot) {
|
|
this.selectedShotId = shot.id
|
|
this.selectedShot = shot
|
|
this.logEvent(`✅ Detail panel opened for: ${shot.name}`)
|
|
|
|
// Close any open dropdowns
|
|
this.shots.forEach(s => s.dropdownOpen = false)
|
|
},
|
|
|
|
closeDetailPanel() {
|
|
this.selectedShotId = null
|
|
this.selectedShot = null
|
|
this.logEvent(`❌ Detail panel closed`)
|
|
},
|
|
|
|
toggleDropdown(shotId, event) {
|
|
event.stopPropagation()
|
|
event.preventDefault()
|
|
|
|
this.logEvent(`🔽 Dropdown toggled for shot ID: ${shotId}`)
|
|
|
|
// Close all dropdowns first
|
|
this.shots.forEach(shot => {
|
|
if (shot.id === shotId) {
|
|
shot.dropdownOpen = !shot.dropdownOpen
|
|
} else {
|
|
shot.dropdownOpen = false
|
|
}
|
|
})
|
|
},
|
|
|
|
editShot(shot, event) {
|
|
event.stopPropagation()
|
|
event.preventDefault()
|
|
this.logEvent(`✏️ Edit shot: ${shot.name}`)
|
|
shot.dropdownOpen = false
|
|
},
|
|
|
|
viewTasks(shot, event) {
|
|
event.stopPropagation()
|
|
event.preventDefault()
|
|
this.logEvent(`📋 View tasks for: ${shot.name}`)
|
|
shot.dropdownOpen = false
|
|
},
|
|
|
|
deleteShot(shot, event) {
|
|
event.stopPropagation()
|
|
event.preventDefault()
|
|
this.logEvent(`🗑️ DELETE SHOT: ${shot.name} - Detail panel should NOT open!`)
|
|
shot.dropdownOpen = false
|
|
|
|
// Simulate delete confirmation dialog
|
|
setTimeout(() => {
|
|
this.logEvent(`📋 Delete confirmation dialog would open for: ${shot.name}`)
|
|
}, 100)
|
|
},
|
|
|
|
preventBubbling(event) {
|
|
event.stopPropagation()
|
|
event.preventDefault()
|
|
},
|
|
|
|
logEvent(message) {
|
|
const log = document.getElementById('eventLog')
|
|
const timestamp = new Date().toLocaleTimeString()
|
|
const entry = document.createElement('div')
|
|
entry.innerHTML = `<span class="text-gray-500">[${timestamp}]</span> ${message}`
|
|
|
|
// Remove placeholder text
|
|
const placeholder = log.querySelector('.text-gray-500')
|
|
if (placeholder && placeholder.textContent.includes('Events will be logged here')) {
|
|
placeholder.remove()
|
|
}
|
|
|
|
log.appendChild(entry)
|
|
log.scrollTop = log.scrollHeight
|
|
},
|
|
|
|
clearLog() {
|
|
const log = document.getElementById('eventLog')
|
|
log.innerHTML = '<div class="text-gray-500">Event log cleared...</div>'
|
|
}
|
|
},
|
|
|
|
mounted() {
|
|
// Close dropdowns when clicking outside
|
|
document.addEventListener('click', (event) => {
|
|
if (!event.target.closest('.relative')) {
|
|
this.shots.forEach(shot => shot.dropdownOpen = false)
|
|
}
|
|
})
|
|
|
|
this.logEvent('🚀 Debug test initialized')
|
|
}
|
|
}).mount('#app')
|
|
</script>
|
|
</body>
|
|
</html> |