Initial commit

This commit is contained in:
2025-08-04 16:33:07 +03:30
commit f798e8e35c
9595 changed files with 1208683 additions and 0 deletions

View File

@@ -0,0 +1,779 @@
<template>
<div class="components-library-container">
<!-- Toggle Button -->
<VBtn
v-if="!isLibraryOpen"
@click="toggleLibrary"
class="library-toggle-btn"
color="primary"
variant="elevated"
size="large"
>
<VIcon icon="tabler-layout-dashboard" size="20" class="me-2" />
Components Library
<VIcon icon="tabler-chevron-up" size="16" class="ms-2" />
</VBtn>
<!-- Components Library Panel -->
<VCard
v-if="isLibraryOpen"
class="components-library-panel"
:class="{ 'panel-open': isLibraryOpen }"
elevation="8"
>
<VCardTitle class="library-header">
<div class="header-content">
<div class="header-left">
<VIcon icon="tabler-layout-dashboard" size="24" class="me-2" />
<span>Dashboard Components</span>
</div>
<VBtn
@click="toggleLibrary"
variant="text"
size="small"
icon
class="close-btn"
>
<VIcon icon="tabler-x" size="20" />
</VBtn>
</div>
</VCardTitle>
<VCardText class="library-content">
<div class="search-section">
<VTextField
v-model="searchQuery"
placeholder="Search components..."
variant="outlined"
density="compact"
prepend-inner-icon="tabler-search"
clearable
class="search-input"
/>
</div>
<div class="categories-tabs">
<VChipGroup
v-model="selectedCategory"
selected-class="text-primary"
class="category-chips"
>
<VChip
v-for="category in categories"
:key="category"
:value="category"
variant="outlined"
size="small"
>
{{ category }}
</VChip>
</VChipGroup>
</div>
<div class="components-grid">
<div
v-for="component in filteredComponents"
:key="component.id"
class="component-item"
:draggable="isEditMode"
@dragstart="handleDragStart($event, component)"
@dragend="handleDragEnd"
:class="{ 'draggable': isEditMode, 'disabled': !isEditMode }"
>
<div class="component-preview">
<VIcon :icon="component.icon" size="32" :color="component.color" />
<div v-if="isEditMode" class="component-overlay">
<VIcon icon="tabler-plus" size="20" color="white" />
</div>
<div v-if="!isEditMode" class="disabled-overlay">
<VIcon icon="tabler-lock" size="16" color="grey" />
</div>
</div>
<div class="component-info">
<h4 class="component-title">{{ component.title }}</h4>
<p class="component-description">{{ component.description }}</p>
<div class="component-meta">
<VChip
:color="component.color"
size="x-small"
variant="outlined"
class="component-type-chip"
>
{{ component.category }}
</VChip>
<VChip
size="x-small"
variant="outlined"
color="grey"
class="size-chip"
>
{{ component.defaultSize.cols }}×{{ Math.round(component.defaultSize.height) }}
</VChip>
</div>
</div>
</div>
</div>
<div v-if="filteredComponents.length === 0" class="no-results">
<VIcon icon="tabler-layout-dashboard" size="48" color="grey-lighten-1" />
<h3>No components found</h3>
<p>Try adjusting your search or category filter</p>
</div>
<div v-if="!isEditMode" class="edit-mode-notice">
<VAlert
type="info"
variant="tonal"
class="ma-2"
>
<template #prepend>
<VIcon icon="tabler-info-circle" />
</template>
<div>
<strong>Edit Mode Required</strong>
<br>
Enable edit mode to add components to your dashboard
</div>
</VAlert>
</div>
</VCardText>
</VCard>
<!-- Drop Zone Overlay -->
<div
v-if="isDragging"
class="drop-zone-overlay"
@dragover="handleDragOver"
@drop="handleDrop"
@dragleave="handleDragLeave"
>
<div class="drop-zone-content">
<VIcon icon="tabler-download" size="64" color="primary" />
<h2>Drop component here to add to dashboard</h2>
<p>Release to add the component to your dashboard</p>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
const props = defineProps({
isEditMode: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['add-component'])
const isLibraryOpen = ref(false)
const isDragging = ref(false)
const searchQuery = ref('')
const selectedCategory = ref('All')
const categories = ['All', 'CRM', 'Ecommerce', 'Analytics', 'Reports', 'Activity']
const availableComponents = ref([
{
id: 'crm-active-project',
title: 'Active Project',
description: 'Shows current active project details',
icon: 'tabler-folder-open',
color: 'primary',
category: 'CRM',
component: 'CrmActiveProject',
defaultSize: { cols: 4, height: 33.33 },
defaultProps: {}
},
{
id: 'crm-activity-timeline',
title: 'Activity Timeline',
description: 'Recent activities and updates timeline',
icon: 'tabler-timeline',
color: 'info',
category: 'Activity',
component: 'CrmActivityTimeline',
defaultSize: { cols: 6, height: 33.33 },
defaultProps: {}
},
{
id: 'crm-analytics-sales',
title: 'Sales Analytics',
description: 'Sales performance and analytics',
icon: 'tabler-chart-line',
color: 'success',
category: 'Analytics',
component: 'CrmAnalyticsSales',
defaultSize: { cols: 4, height: 33.33 },
defaultProps: {}
},
{
id: 'crm-earning-reports',
title: 'Earning Reports',
description: 'Yearly earning overview and reports',
icon: 'tabler-report-money',
color: 'warning',
category: 'Reports',
component: 'CrmEarningReportsYearlyOverview',
defaultSize: { cols: 8, height: 33.33 },
defaultProps: {}
},
{
id: 'crm-project-status',
title: 'Project Status',
description: 'Current status of all projects',
icon: 'tabler-checkup-list',
color: 'deep-purple',
category: 'CRM',
component: 'CrmProjectStatus',
defaultSize: { cols: 4, height: 33.33 },
defaultProps: {}
},
{
id: 'crm-recent-transactions',
title: 'Recent Transactions',
description: 'Latest financial transactions',
icon: 'tabler-credit-card',
color: 'teal',
category: 'CRM',
component: 'CrmRecentTransactions',
defaultSize: { cols: 6, height: 33.33 },
defaultProps: {}
},
{
id: 'crm-sales-countries',
title: 'Sales by Countries',
description: 'Geographic sales distribution',
icon: 'tabler-world',
color: 'indigo',
category: 'Analytics',
component: 'CrmSalesByCountries',
defaultSize: { cols: 4, height: 33.33 },
defaultProps: {}
},
{
id: 'project-activity-bar',
title: 'Project Activity Chart',
description: 'Bar chart showing project activities',
icon: 'tabler-chart-bar',
color: 'cyan',
category: 'Analytics',
component: 'ProjectActivityBarChart',
defaultSize: { cols: 8, height: 33.33 },
defaultProps: {}
},
{
id: 'analysis-card-1',
title: 'Analysis Card (Projects)',
description: 'Active projects progress analysis',
icon: 'tabler-analytics',
color: 'pink',
category: 'Analytics',
component: 'AnalysisCard',
defaultSize: { cols: 4, height: 33.33 },
defaultProps: { chartName: 'Active Projects Progress' }
},
{
id: 'analysis-card-2',
title: 'Analysis Card (Cost)',
description: 'Cost overview analysis',
icon: 'tabler-analytics',
color: 'pink',
category: 'Analytics',
component: 'AnalysisCard',
defaultSize: { cols: 4, height: 33.33 },
defaultProps: { chartName: 'Cost overview' }
},
{
id: 'cost-overview',
title: 'Cost Overview',
description: 'Detailed cost breakdown and overview',
icon: 'tabler-coin',
color: 'orange',
category: 'Reports',
component: 'CostOverview',
defaultSize: { cols: 8, height: 33.33 },
defaultProps: {}
},
{
id: 'generated-leads-1',
title: 'Generated Leads (Primary)',
description: 'Lead generation progress - Primary theme',
icon: 'tabler-users',
color: 'primary',
category: 'Ecommerce',
component: 'GeneratedLeadsCard',
defaultSize: { cols: 4, height: 33.33 },
defaultProps: { progress: 33 }
},
{
id: 'generated-leads-2',
title: 'Generated Leads (Success)',
description: 'Lead generation progress - Success theme',
icon: 'tabler-users',
color: 'success',
category: 'Ecommerce',
component: 'GeneratedLeadsCard',
defaultSize: { cols: 4, height: 33.33 },
defaultProps: { donutColors: ["primary"], progress: 71 }
},
{
id: 'generated-leads-3',
title: 'Generated Leads (Warning)',
description: 'Lead generation progress - Warning theme',
icon: 'tabler-users',
color: 'warning',
category: 'Ecommerce',
component: 'GeneratedLeadsCard',
defaultSize: { cols: 4, height: 33.33 },
defaultProps: { donutColors: ["warning"], progress: 56 }
}
])
const filteredComponents = computed(() => {
let components = availableComponents.value
// Filter by category
if (selectedCategory.value !== 'All') {
components = components.filter(comp => comp.category === selectedCategory.value)
}
// Filter by search query
if (searchQuery.value) {
const query = searchQuery.value.toLowerCase()
components = components.filter(comp =>
comp.title.toLowerCase().includes(query) ||
comp.description.toLowerCase().includes(query) ||
comp.category.toLowerCase().includes(query)
)
}
return components
})
const toggleLibrary = () => {
isLibraryOpen.value = !isLibraryOpen.value
}
const handleDragStart = (event, component) => {
if (!props.isEditMode) {
event.preventDefault()
return
}
isDragging.value = true
event.dataTransfer.setData('application/json', JSON.stringify(component))
event.dataTransfer.effectAllowed = 'copy'
// Create drag image
const dragImage = event.target.cloneNode(true)
dragImage.style.transform = 'rotate(5deg) scale(0.8)'
dragImage.style.opacity = '0.8'
dragImage.style.zIndex = '9999'
document.body.appendChild(dragImage)
event.dataTransfer.setDragImage(dragImage, 50, 50)
setTimeout(() => {
if (document.body.contains(dragImage)) {
document.body.removeChild(dragImage)
}
}, 0)
}
const handleDragEnd = () => {
isDragging.value = false
}
const handleDragOver = (event) => {
event.preventDefault()
event.dataTransfer.dropEffect = 'copy'
}
const handleDrop = (event) => {
event.preventDefault()
isDragging.value = false
try {
const componentData = JSON.parse(event.dataTransfer.getData('application/json'))
emit('add-component', componentData)
// Show success feedback
console.log('Component added successfully:', componentData.title)
} catch (error) {
console.error('Error adding component:', error)
}
}
const handleDragLeave = (event) => {
// Only hide if we're leaving the drop zone entirely
if (!event.currentTarget.contains(event.relatedTarget)) {
isDragging.value = false
}
}
// Close library when clicking outside
const handleClickOutside = (event) => {
if (isLibraryOpen.value && !event.target.closest('.components-library-panel, .library-toggle-btn')) {
isLibraryOpen.value = false
}
}
onMounted(() => {
document.addEventListener('click', handleClickOutside)
})
onUnmounted(() => {
document.removeEventListener('click', handleClickOutside)
})
</script>
<style scoped>
.components-library-container {
position: fixed;
bottom: 2rem;
right: 2rem;
z-index: 1000;
}
.library-toggle-btn {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1), 0 8px 32px rgba(var(--v-theme-primary), 0.2) !important;
border-radius: 12px !important;
padding: 12px 20px !important;
font-weight: 600 !important;
text-transform: none !important;
backdrop-filter: blur(10px);
animation: bounceIn 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
.library-toggle-btn:hover {
transform: translateY(-2px) scale(1.02);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15), 0 12px 40px rgba(var(--v-theme-primary), 0.3) !important;
}
.components-library-panel {
position: absolute;
bottom: 80px;
right: 0;
width: 450px;
max-height: 600px;
border-radius: 16px !important;
backdrop-filter: blur(20px);
background: rgba(255, 255, 255, 0.95) !important;
border: 1px solid rgba(255, 255, 255, 0.2);
animation: slideUp 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.library-header {
background: linear-gradient(135deg, rgba(var(--v-theme-primary), 0.1), rgba(var(--v-theme-secondary), 0.05));
border-radius: 16px 16px 0 0;
padding: 1rem 1.5rem;
}
.header-content {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.header-left {
display: flex;
align-items: center;
font-weight: 600;
font-size: 1.1rem;
}
.close-btn {
opacity: 0.7;
transition: all 0.2s ease;
}
.close-btn:hover {
opacity: 1;
transform: scale(1.1);
}
.library-content {
max-height: 500px;
overflow-y: auto;
padding: 1rem 1.5rem;
}
.search-section {
margin-bottom: 1rem;
}
.search-input {
border-radius: 12px;
}
.categories-tabs {
margin-bottom: 1.5rem;
}
.category-chips {
gap: 0.5rem;
}
.components-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
}
.component-item {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 12px;
background: rgba(255, 255, 255, 0.7);
transition: all 0.3s ease;
position: relative;
}
.component-item.draggable {
cursor: grab;
}
.component-item.draggable:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
border-color: rgba(var(--v-theme-primary), 0.3);
background: rgba(var(--v-theme-primary), 0.02);
}
.component-item.disabled {
opacity: 0.6;
cursor: not-allowed;
}
.component-item:active.draggable {
cursor: grabbing;
transform: scale(0.98);
}
.component-preview {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 60px;
height: 60px;
background: rgba(var(--v-theme-surface), 0.8);
border-radius: 12px;
border: 1px solid rgba(0, 0, 0, 0.1);
flex-shrink: 0;
}
.component-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(var(--v-theme-primary), 0.9);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: all 0.2s ease;
}
.disabled-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(128, 128, 128, 0.3);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
}
.component-item.draggable:hover .component-overlay {
opacity: 1;
}
.component-info {
flex: 1;
min-width: 0;
}
.component-title {
font-size: 0.95rem;
font-weight: 600;
margin: 0 0 0.25rem 0;
color: rgba(0, 0, 0, 0.87);
line-height: 1.2;
}
.component-description {
font-size: 0.8rem;
color: rgba(0, 0, 0, 0.6);
margin: 0 0 0.5rem 0;
line-height: 1.3;
}
.component-meta {
display: flex;
gap: 0.5rem;
align-items: center;
}
.component-type-chip,
.size-chip {
font-size: 0.7rem !important;
height: 20px !important;
}
.no-results {
text-align: center;
padding: 2rem 1rem;
color: rgba(0, 0, 0, 0.6);
}
.no-results h3 {
margin: 1rem 0 0.5rem 0;
font-size: 1.1rem;
}
.no-results p {
margin: 0;
font-size: 0.9rem;
}
.edit-mode-notice {
margin-top: 1rem;
}
.drop-zone-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(var(--v-theme-primary), 0.1);
backdrop-filter: blur(4px);
z-index: 999;
display: flex;
align-items: center;
justify-content: center;
animation: fadeIn 0.2s ease;
}
.drop-zone-content {
text-align: center;
padding: 3rem;
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
border: 3px dashed rgba(var(--v-theme-primary), 0.5);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
animation: dropZonePulse 1.5s ease-in-out infinite;
}
.drop-zone-content h2 {
margin: 1rem 0 0.5rem 0;
color: rgba(var(--v-theme-primary), 0.9);
font-size: 1.5rem;
font-weight: 600;
}
.drop-zone-content p {
margin: 0;
color: rgba(0, 0, 0, 0.7);
font-size: 1rem;
}
@keyframes bounceIn {
0% {
opacity: 0;
transform: scale(0.3) translateY(100px);
}
50% {
opacity: 1;
transform: scale(1.05);
}
70% {
transform: scale(0.9);
}
100% {
opacity: 1;
transform: scale(1) translateY(0);
}
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes dropZonePulse {
0%, 100% {
transform: scale(1);
border-color: rgba(var(--v-theme-primary), 0.5);
}
50% {
transform: scale(1.02);
border-color: rgba(var(--v-theme-primary), 0.8);
}
}
@media (max-width: 768px) {
.components-library-container {
bottom: 1rem;
right: 1rem;
left: 1rem;
}
.components-library-panel {
width: 100%;
max-width: none;
right: 0;
left: 0;
}
.library-toggle-btn {
width: 100%;
justify-content: center;
}
.component-item {
padding: 0.75rem;
}
.component-preview {
width: 50px;
height: 50px;
}
.component-title {
font-size: 0.9rem;
}
.component-description {
font-size: 0.75rem;
}
}
</style>