|
99 | 99 | <div class="flex items-center justify-between mb-6">
|
100 | 100 | <div>
|
101 | 101 | <h3 class="text-base font-medium text-gray-900 dark:text-gray-100">User Model Relationships</h3>
|
102 |
| - <p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Interactive diagram showing User model relationships. Click on any model to navigate to its details.</p> |
| 102 | + <p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Interactive diagram showing User model relationships. Click on any model to view details.</p> |
103 | 103 | </div>
|
104 | 104 | </div>
|
105 |
| - <div ref="diagramContainer" class="h-[400px] relative"> |
106 |
| - <!-- D3 diagram will be rendered here --> |
| 105 | + <div class="flex"> |
| 106 | + <div ref="diagramContainer" class="h-[400px] relative flex-1"> |
| 107 | + <!-- D3 diagram will be rendered here --> |
| 108 | + </div> |
| 109 | + |
| 110 | + <div v-if="selectedModel" class="w-64 ml-6 p-4 bg-gray-50 dark:bg-blue-gray-600 rounded-lg"> |
| 111 | + <div class="flex items-center mb-4"> |
| 112 | + <span class="text-2xl mr-2">{{ selectedModel.emoji }}</span> |
| 113 | + <h4 class="text-lg font-semibold text-gray-900 dark:text-gray-100">{{ selectedModel.name }}</h4> |
| 114 | + </div> |
| 115 | + |
| 116 | + <div class="mb-4"> |
| 117 | + <h5 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Properties</h5> |
| 118 | + <ul class="space-y-1"> |
| 119 | + <li v-for="prop in selectedModel.properties" :key="prop" class="text-sm text-gray-600 dark:text-gray-400 font-mono"> |
| 120 | + {{ prop }} |
| 121 | + </li> |
| 122 | + </ul> |
| 123 | + </div> |
| 124 | + |
| 125 | + <div class="mb-6"> |
| 126 | + <h5 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Relationships</h5> |
| 127 | + <ul class="space-y-1"> |
| 128 | + <li v-for="rel in selectedModel.relationships" :key="rel" class="text-sm text-gray-600 dark:text-gray-400 font-mono"> |
| 129 | + {{ rel }} |
| 130 | + </li> |
| 131 | + </ul> |
| 132 | + </div> |
| 133 | + |
| 134 | + <router-link |
| 135 | + v-if="selectedModel.id === 'user'" |
| 136 | + :to="getModelRoute('team')" |
| 137 | + class="flex items-center justify-center w-full px-4 py-2 text-sm font-semibold text-white bg-blue-600 hover:bg-blue-500 rounded-md shadow-sm transition-colors duration-150 group" |
| 138 | + > |
| 139 | + Next: Team Model |
| 140 | + <svg class="w-4 h-4 ml-2 transition-transform group-hover:translate-x-1" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| 141 | + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /> |
| 142 | + </svg> |
| 143 | + </router-link> |
| 144 | + |
| 145 | + <router-link |
| 146 | + v-else |
| 147 | + :to="getModelRoute(selectedModel.id)" |
| 148 | + class="block w-full text-center px-4 py-2 text-sm font-semibold text-white bg-blue-600 hover:bg-blue-500 rounded-md shadow-sm transition-colors duration-150" |
| 149 | + > |
| 150 | + View Details |
| 151 | + </router-link> |
| 152 | + </div> |
107 | 153 | </div>
|
108 | 154 | </div>
|
109 | 155 | </div>
|
@@ -381,104 +427,11 @@ watch(timeRange, async () => {
|
381 | 427 | // Initial load
|
382 | 428 | onMounted(async () => {
|
383 | 429 | isLoading.value = true
|
| 430 | + // Set User as the default selected model |
| 431 | + selectedModel.value = models.find(model => model.id === 'user') || null |
384 | 432 | await new Promise(resolve => setTimeout(resolve, 500))
|
385 | 433 | isLoading.value = false
|
386 |
| -}) |
387 |
| -
|
388 |
| -// Get router instance |
389 |
| -const router = useRouter() |
390 |
| -
|
391 |
| -// Model node interface |
392 |
| -interface ModelNode extends d3.SimulationNodeDatum { |
393 |
| - id: string |
394 |
| - name: string |
395 |
| - properties: string[] |
396 |
| - relationships: string[] |
397 |
| - emoji: string |
398 |
| - color: string |
399 |
| - x?: number |
400 |
| - y?: number |
401 |
| - fx?: number | null |
402 |
| - fy?: number | null |
403 |
| -} |
404 |
| -
|
405 |
| -// Relationship link interface |
406 |
| -interface RelationshipLink { |
407 |
| - source: string | ModelNode |
408 |
| - target: string | ModelNode |
409 |
| - type: 'hasMany' | 'belongsTo' | 'hasOne' | 'belongsToMany' |
410 |
| -} |
411 |
| -
|
412 |
| -// User model and its relationships |
413 |
| -const models: ModelNode[] = [ |
414 |
| - { |
415 |
| - id: 'user', |
416 |
| - name: 'User', |
417 |
| - properties: ['id', 'name', 'email', 'password'], |
418 |
| - relationships: ['teams', 'accessTokens', 'activities', 'posts'], |
419 |
| - emoji: '👤', |
420 |
| - color: '#2563EB' |
421 |
| - }, |
422 |
| - { |
423 |
| - id: 'team', |
424 |
| - name: 'Team', |
425 |
| - properties: ['id', 'name', 'owner_id'], |
426 |
| - relationships: ['users'], |
427 |
| - emoji: '👥', |
428 |
| - color: '#60A5FA' |
429 |
| - }, |
430 |
| - { |
431 |
| - id: 'accessToken', |
432 |
| - name: 'AccessToken', |
433 |
| - properties: ['id', 'token', 'user_id'], |
434 |
| - relationships: ['user'], |
435 |
| - emoji: '🔑', |
436 |
| - color: '#3B82F6' |
437 |
| - }, |
438 |
| - { |
439 |
| - id: 'activity', |
440 |
| - name: 'Activity', |
441 |
| - properties: ['id', 'type', 'user_id'], |
442 |
| - relationships: ['user'], |
443 |
| - emoji: '📊', |
444 |
| - color: '#93C5FD' |
445 |
| - }, |
446 |
| - { |
447 |
| - id: 'post', |
448 |
| - name: 'Post', |
449 |
| - properties: ['id', 'title', 'content'], |
450 |
| - relationships: ['user'], |
451 |
| - emoji: '📝', |
452 |
| - color: '#BFDBFE' |
453 |
| - } |
454 |
| -] |
455 |
| -
|
456 |
| -// Define relationships |
457 |
| -const relationships: RelationshipLink[] = [ |
458 |
| - { source: 'user', target: 'team', type: 'belongsToMany' }, |
459 |
| - { source: 'team', target: 'user', type: 'belongsToMany' }, |
460 |
| - { source: 'user', target: 'accessToken', type: 'hasMany' }, |
461 |
| - { source: 'user', target: 'activity', type: 'hasMany' }, |
462 |
| - { source: 'post', target: 'user', type: 'belongsTo' } |
463 |
| -] |
464 |
| -
|
465 |
| -// Visualization state |
466 |
| -const diagramContainer = ref<HTMLElement | null>(null) |
467 |
| -let simulation: d3.Simulation<ModelNode, undefined> |
468 |
| -
|
469 |
| -// Function to get route path for a model |
470 |
| -const getModelRoute = (modelId: string) => { |
471 |
| - const routes: Record<string, string> = { |
472 |
| - user: '/models/users', |
473 |
| - team: '/models/teams', |
474 |
| - accessToken: '/models/access-tokens', |
475 |
| - activity: '/models/activities', |
476 |
| - post: '/models/posts' |
477 |
| - } |
478 |
| - return routes[modelId] || '/models' |
479 |
| -} |
480 | 434 |
|
481 |
| -onMounted(() => { |
482 | 435 | if (!diagramContainer.value) return
|
483 | 436 |
|
484 | 437 | const width = 800
|
@@ -534,8 +487,12 @@ onMounted(() => {
|
534 | 487 | .on('end', dragended))
|
535 | 488 | .style('cursor', 'pointer') // Add pointer cursor
|
536 | 489 | .on('click', (event, d) => {
|
537 |
| - // Navigate to the model's page |
538 |
| - router.push(getModelRoute(d.id)) |
| 490 | + // Set the selected model |
| 491 | + selectedModel.value = d |
| 492 | + // Navigate to the model's page on double click |
| 493 | + if (event.detail === 2) { |
| 494 | + router.push(getModelRoute(d.id)) |
| 495 | + } |
539 | 496 | })
|
540 | 497 |
|
541 | 498 | // Add hover effect to nodes
|
@@ -603,6 +560,102 @@ onMounted(() => {
|
603 | 560 | event.subject.fy = null
|
604 | 561 | }
|
605 | 562 | })
|
| 563 | +
|
| 564 | +// Get router instance |
| 565 | +const router = useRouter() |
| 566 | +
|
| 567 | +// Model node interface |
| 568 | +interface ModelNode extends d3.SimulationNodeDatum { |
| 569 | + id: string |
| 570 | + name: string |
| 571 | + properties: string[] |
| 572 | + relationships: string[] |
| 573 | + emoji: string |
| 574 | + color: string |
| 575 | + x?: number |
| 576 | + y?: number |
| 577 | + fx?: number | null |
| 578 | + fy?: number | null |
| 579 | +} |
| 580 | +
|
| 581 | +// Relationship link interface |
| 582 | +interface RelationshipLink { |
| 583 | + source: string | ModelNode |
| 584 | + target: string | ModelNode |
| 585 | + type: 'hasMany' | 'belongsTo' | 'hasOne' | 'belongsToMany' |
| 586 | +} |
| 587 | +
|
| 588 | +// Add selectedModel ref before the models definition |
| 589 | +const selectedModel = ref<ModelNode | null>(null) |
| 590 | +
|
| 591 | +// User model and its relationships |
| 592 | +const models: ModelNode[] = [ |
| 593 | + { |
| 594 | + id: 'user', |
| 595 | + name: 'User', |
| 596 | + properties: ['id', 'name', 'email', 'password'], |
| 597 | + relationships: ['teams', 'accessTokens', 'activities', 'posts'], |
| 598 | + emoji: '👤', |
| 599 | + color: '#2563EB' |
| 600 | + }, |
| 601 | + { |
| 602 | + id: 'team', |
| 603 | + name: 'Team', |
| 604 | + properties: ['id', 'name', 'owner_id'], |
| 605 | + relationships: ['users'], |
| 606 | + emoji: '👥', |
| 607 | + color: '#60A5FA' |
| 608 | + }, |
| 609 | + { |
| 610 | + id: 'accessToken', |
| 611 | + name: 'AccessToken', |
| 612 | + properties: ['id', 'token', 'user_id'], |
| 613 | + relationships: ['user'], |
| 614 | + emoji: '🔑', |
| 615 | + color: '#3B82F6' |
| 616 | + }, |
| 617 | + { |
| 618 | + id: 'activity', |
| 619 | + name: 'Activity', |
| 620 | + properties: ['id', 'type', 'user_id'], |
| 621 | + relationships: ['user'], |
| 622 | + emoji: '📊', |
| 623 | + color: '#93C5FD' |
| 624 | + }, |
| 625 | + { |
| 626 | + id: 'post', |
| 627 | + name: 'Post', |
| 628 | + properties: ['id', 'title', 'content'], |
| 629 | + relationships: ['user'], |
| 630 | + emoji: '📝', |
| 631 | + color: '#BFDBFE' |
| 632 | + } |
| 633 | +] |
| 634 | +
|
| 635 | +// Define relationships |
| 636 | +const relationships: RelationshipLink[] = [ |
| 637 | + { source: 'user', target: 'team', type: 'belongsToMany' }, |
| 638 | + { source: 'team', target: 'user', type: 'belongsToMany' }, |
| 639 | + { source: 'user', target: 'accessToken', type: 'hasMany' }, |
| 640 | + { source: 'user', target: 'activity', type: 'hasMany' }, |
| 641 | + { source: 'post', target: 'user', type: 'belongsTo' } |
| 642 | +] |
| 643 | +
|
| 644 | +// Visualization state |
| 645 | +const diagramContainer = ref<HTMLElement | null>(null) |
| 646 | +let simulation: d3.Simulation<ModelNode, undefined> |
| 647 | +
|
| 648 | +// Function to get route path for a model |
| 649 | +const getModelRoute = (modelId: string) => { |
| 650 | + const routes: Record<string, string> = { |
| 651 | + user: '/models/users', |
| 652 | + team: '/models/teams', |
| 653 | + accessToken: '/models/access-tokens', |
| 654 | + activity: '/models/activities', |
| 655 | + post: '/models/posts' |
| 656 | + } |
| 657 | + return routes[modelId] || '/models' |
| 658 | +} |
606 | 659 | </script>
|
607 | 660 |
|
608 | 661 | <style scoped>
|
|
0 commit comments