960 lines
26 KiB
Vue
960 lines
26 KiB
Vue
<script setup>
|
|
import { ref, computed, nextTick, onMounted, watch, onUnmounted } from "vue";
|
|
|
|
import CrmActiveProject from "@/views/dashboards/crm/CrmActiveProject.vue";
|
|
import CrmActivityTimeline from "@/views/dashboards/crm/CrmActivityTimeline.vue";
|
|
import CrmAnalyticsSales from "@/views/dashboards/crm/CrmAnalyticsSales.vue";
|
|
import CrmEarningReportsYearlyOverview from "@/views/dashboards/crm/CrmEarningReportsYearlyOverview.vue";
|
|
import CrmProjectStatus from "@/views/dashboards/crm/CrmProjectStatus.vue";
|
|
import CrmRecentTransactions from "@/views/dashboards/crm/CrmRecentTransactions.vue";
|
|
import CrmSalesByCountries from "@/views/dashboards/crm/CrmSalesByCountries.vue";
|
|
|
|
import ProjectActivityBarChart from "@/components/ProjectActivityBarChart.vue";
|
|
import AnalysisCard from "@/components/AnalysisCard.vue";
|
|
import CostOverview from "@/components/CostOverview.vue";
|
|
import GeneratedLeadsCard from "@/views/dashboards/ecommerce/EcommerceGeneratedLeads.vue";
|
|
import GanttChart from "./gantt.vue";
|
|
|
|
import EcommerceCongratulationsJohn from '@/views/dashboards/ecommerce/EcommerceCongratulationsJohn.vue'
|
|
import EcommerceEarningReports from '@/views/dashboards/ecommerce/EcommerceEarningReports.vue'
|
|
import EcommerceExpensesRadialBarCharts from '@/views/dashboards/ecommerce/EcommerceExpensesRadialBarCharts.vue'
|
|
import EcommerceInvoiceTable from '@/views/dashboards/ecommerce/EcommerceInvoiceTable.vue'
|
|
import EcommerceOrder from '@/views/dashboards/ecommerce/EcommerceOrder.vue'
|
|
import EcommercePopularProducts from '@/views/dashboards/ecommerce/EcommercePopularProducts.vue'
|
|
import EcommerceRevenueReport from '@/views/dashboards/ecommerce/EcommerceRevenueReport.vue'
|
|
import EcommerceStatistics from '@/views/dashboards/ecommerce/EcommerceStatistics.vue'
|
|
import EcommerceTotalProfitLineCharts from '@/views/dashboards/ecommerce/EcommerceTotalProfitLineCharts.vue'
|
|
import EcommerceTransactions from '@/views/dashboards/ecommerce/EcommerceTransactions.vue'
|
|
|
|
|
|
const isEditMode = ref(false);
|
|
|
|
const handleEditModeChange = (event) => {
|
|
if (event.detail) {
|
|
isEditMode.value = event.detail.isActive;
|
|
}
|
|
};
|
|
|
|
const handleDropFromSidebar = async (e) => {
|
|
console.log('Drop event triggered', { isEditMode: isEditMode.value });
|
|
if (!isEditMode.value) return;
|
|
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
const widgetId = e.dataTransfer.getData("text/plain");
|
|
const widgetData = e.dataTransfer.getData("application/json");
|
|
|
|
console.log('Widget ID from dataTransfer:', widgetId);
|
|
console.log('Widget Data:', widgetData);
|
|
|
|
if (widgetId && !cardOrder.value.includes(widgetId)) {
|
|
console.log('Adding widget to dashboard:', widgetId);
|
|
|
|
if (widgetData && !cardComponents[widgetId]) {
|
|
const widget = JSON.parse(widgetData);
|
|
|
|
cardComponents[widgetId] = {
|
|
component: GeneratedLeadsCard,
|
|
props: widget.props || {}
|
|
};
|
|
|
|
if (!cardSizes.value[widgetId]) {
|
|
cardSizes.value[widgetId] = { cols: 6, height: 33.33 };
|
|
}
|
|
}
|
|
|
|
cardOrder.value.push(widgetId);
|
|
await nextTick();
|
|
await calculateRowHeights();
|
|
|
|
const event = new CustomEvent('widget-added-to-dashboard', {
|
|
detail: { widgetId }
|
|
});
|
|
window.dispatchEvent(event);
|
|
|
|
window.dispatchEvent(new CustomEvent('dashboard-widgets-state', {
|
|
detail: { widgets: cardOrder.value }
|
|
}));
|
|
}
|
|
}
|
|
|
|
const handleDragOverFromSidebar = (e) => {
|
|
if (!isEditMode.value) return;
|
|
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
e.dataTransfer.dropEffect = "copy";
|
|
|
|
const container = e.currentTarget;
|
|
container.classList.add('drag-over');
|
|
};
|
|
|
|
const handleDragLeaveFromSidebar = (e) => {
|
|
if (!isEditMode.value) return;
|
|
|
|
const container = e.currentTarget;
|
|
container.classList.remove('drag-over');
|
|
};
|
|
|
|
const cardOrder = ref([
|
|
"leads1",
|
|
"leads2",
|
|
"leads3",
|
|
"project-activity",
|
|
"analysis1",
|
|
"analysis2",
|
|
"cost-overview",
|
|
"earning-reports",
|
|
"analytics-sales",
|
|
"sales-countries",
|
|
"project-status",
|
|
"active-project",
|
|
"recent-transactions",
|
|
"activity-timeline",
|
|
]);
|
|
|
|
const defaultWidgetIds = [
|
|
"leads1",
|
|
"leads2",
|
|
"leads3",
|
|
"project-activity",
|
|
"analysis1",
|
|
"analysis2",
|
|
"cost-overview",
|
|
"earning-reports",
|
|
"analytics-sales",
|
|
"sales-countries",
|
|
"project-status",
|
|
"active-project",
|
|
"recent-transactions",
|
|
"activity-timeline"
|
|
];
|
|
|
|
const cardSizes = ref({
|
|
leads1: { cols: 4, height: 33.33 },
|
|
leads2: { cols: 4, height: 33.33 },
|
|
leads3: { cols: 4, height: 33.33 },
|
|
"project-activity": { cols: 8, height: 33.33 },
|
|
analysis1: { cols: 4, height: 33.33 },
|
|
analysis2: { cols: 4, height: 33.33 },
|
|
"cost-overview": { cols: 8, height: 33.33 },
|
|
"earning-reports": { cols: 8, height: 33.33 },
|
|
"analytics-sales": { cols: 4, height: 33.33 },
|
|
"sales-countries": { cols: 4, height: 33.33 },
|
|
"project-status": { cols: 4, height: 33.33 },
|
|
"active-project": { cols: 4, height: 33.33 },
|
|
"recent-transactions": { cols: 6, height: 33.33 },
|
|
"activity-timeline": { cols: 6, height: 33.33 },
|
|
"ecommerce-congratulations": { cols: 6, height: 33.33 },
|
|
"ecommerce-earning-reports": { cols: 8, height: 33.33 },
|
|
"ecommerce-expenses": { cols: 4, height: 33.33 },
|
|
"ecommerce-generated-leads": { cols: 4, height: 33.33 },
|
|
"ecommerce-invoice-table": { cols: 12, height: 33.33 },
|
|
"ecommerce-order": { cols: 6, height: 33.33 },
|
|
"ecommerce-popular-products": { cols: 6, height: 33.33 },
|
|
"ecommerce-revenue-report": { cols: 8, height: 33.33 },
|
|
"ecommerce-statistics": { cols: 12, height: 33.33 },
|
|
"ecommerce-total-profit": { cols: 6, height: 33.33 },
|
|
"ecommerce-transactions": { cols: 8, height: 33.33 },
|
|
|
|
});
|
|
|
|
const cardComponents = {
|
|
leads1: { component: GeneratedLeadsCard, props: { progress: 33 } },
|
|
leads2: {
|
|
component: GeneratedLeadsCard,
|
|
props: { donutColors: ["primary"], progress: 71 },
|
|
},
|
|
leads3: {
|
|
component: GeneratedLeadsCard,
|
|
props: { donutColors: ["warning"], progress: 56 },
|
|
},
|
|
"project-activity": { component: ProjectActivityBarChart, props: {} },
|
|
analysis1: {
|
|
component: AnalysisCard,
|
|
props: { chartName: "Active Projects Progress" },
|
|
},
|
|
analysis2: { component: AnalysisCard, props: { chartName: "Cost overview" } },
|
|
"cost-overview": { component: CostOverview, props: {} },
|
|
"earning-reports": { component: CrmEarningReportsYearlyOverview, props: {} },
|
|
"analytics-sales": { component: CrmAnalyticsSales, props: {} },
|
|
"sales-countries": { component: CrmSalesByCountries, props: {} },
|
|
"project-status": { component: CrmProjectStatus, props: {} },
|
|
"active-project": { component: CrmActiveProject, props: {} },
|
|
"recent-transactions": { component: CrmRecentTransactions, props: {} },
|
|
"activity-timeline": { component: CrmActivityTimeline, props: {} },
|
|
"ecommerce-congratulations": { component: EcommerceCongratulationsJohn, props: {} },
|
|
"ecommerce-earning-reports": { component: EcommerceEarningReports, props: {} },
|
|
"ecommerce-expenses": { component: EcommerceExpensesRadialBarCharts, props: {} },
|
|
"ecommerce-invoice-table": { component: EcommerceInvoiceTable, props: {} },
|
|
"ecommerce-order": { component: EcommerceOrder, props: {} },
|
|
"ecommerce-popular-products": { component: EcommercePopularProducts, props: {} },
|
|
"ecommerce-revenue-report": { component: EcommerceRevenueReport, props: {} },
|
|
"ecommerce-statistics": { component: EcommerceStatistics, props: {} },
|
|
"ecommerce-total-profit": { component: EcommerceTotalProfitLineCharts, props: {} },
|
|
"ecommerce-transactions": { component: EcommerceTransactions, props: {} },
|
|
};
|
|
|
|
window.addEventListener('remove-widget-from-dashboard', async (event) => {
|
|
const widgetId = event.detail?.widgetId
|
|
if (!widgetId) return
|
|
const idx = cardOrder.value.indexOf(widgetId)
|
|
if (idx > -1) {
|
|
cardOrder.value.splice(idx, 1)
|
|
await nextTick()
|
|
await calculateRowHeights()
|
|
window.dispatchEvent(new CustomEvent('dashboard-widgets-state', {
|
|
detail: { widgets: cardOrder.value }
|
|
}))
|
|
}
|
|
})
|
|
|
|
const handleDeleteCard = async (cardId) => {
|
|
const cardIndex = cardOrder.value.indexOf(cardId)
|
|
if (cardIndex > -1) {
|
|
cardOrder.value.splice(cardIndex, 1)
|
|
await nextTick()
|
|
await calculateRowHeights()
|
|
window.dispatchEvent(new CustomEvent('dashboard-widgets-state', {
|
|
detail: { widgets: cardOrder.value }
|
|
}))
|
|
}
|
|
}
|
|
|
|
const handleAddWidgetToDashboard = async (event) => {
|
|
if (event.detail && event.detail.widgetId) {
|
|
const widgetId = event.detail.widgetId;
|
|
const widget = event.detail.widget;
|
|
|
|
if (!cardComponents[widgetId] && widget.component) {
|
|
cardComponents[widgetId] = {
|
|
component: widget.component,
|
|
props: widget.props || {}
|
|
};
|
|
|
|
if (!cardSizes.value[widgetId]) {
|
|
cardSizes.value[widgetId] = widget.size || { cols: 6, height: 33.33 };
|
|
}
|
|
}
|
|
|
|
if (!cardOrder.value.includes(widgetId)) {
|
|
cardOrder.value.unshift(widgetId);
|
|
await nextTick();
|
|
await calculateRowHeights();
|
|
}
|
|
}
|
|
};
|
|
|
|
const restoreAllCards = async () => {
|
|
cardOrder.value = [
|
|
"leads1",
|
|
"leads2",
|
|
"leads3",
|
|
"project-activity",
|
|
"gantt-chart",
|
|
"analysis1",
|
|
"analysis2",
|
|
"cost-overview",
|
|
"earning-reports",
|
|
"analytics-sales",
|
|
"sales-countries",
|
|
"project-status",
|
|
"active-project",
|
|
"recent-transactions",
|
|
"activity-timeline",
|
|
];
|
|
cardOrder.value = defaultWidgetIds.slice();
|
|
await nextTick();
|
|
await calculateRowHeights();
|
|
window.dispatchEvent(new CustomEvent('dashboard-widgets-state', {
|
|
detail: { widgets: cardOrder.value }
|
|
}));
|
|
};
|
|
|
|
const cardRows = computed(() => {
|
|
const rows = [];
|
|
let currentRow = [];
|
|
let currentRowCols = 0;
|
|
cardOrder.value.forEach((cardId) => {
|
|
const cardCols = cardSizes.value[cardId].cols;
|
|
if (currentRowCols + cardCols > 12) {
|
|
if (currentRow.length > 0) {
|
|
rows.push([...currentRow]);
|
|
}
|
|
currentRow = [cardId];
|
|
currentRowCols = cardCols;
|
|
} else {
|
|
currentRow.push(cardId);
|
|
currentRowCols += cardCols;
|
|
}
|
|
});
|
|
if (currentRow.length > 0) {
|
|
rows.push(currentRow);
|
|
}
|
|
return rows;
|
|
});
|
|
|
|
const rowHeights = ref({});
|
|
|
|
const calculateRowHeights = async () => {
|
|
await nextTick();
|
|
const newRowHeights = {};
|
|
cardRows.value.forEach((row, rowIndex) => {
|
|
let maxHeight = 0;
|
|
row.forEach((cardId) => {
|
|
const cardElement = document.querySelector(
|
|
`[data-card-id="${cardId}"] .card-content`
|
|
);
|
|
if (cardElement) {
|
|
const height = cardElement.scrollHeight;
|
|
maxHeight = Math.max(maxHeight, height);
|
|
}
|
|
});
|
|
if (maxHeight > 0) {
|
|
newRowHeights[rowIndex] = `${maxHeight}px`;
|
|
}
|
|
});
|
|
rowHeights.value = newRowHeights;
|
|
};
|
|
|
|
const getCardHeight = (cardId) => {
|
|
if (cardSizes.value[cardId].height !== "auto") {
|
|
return cardSizes.value[cardId].height;
|
|
}
|
|
return "auto";
|
|
};
|
|
|
|
let draggedElement = null;
|
|
let draggedIndex = -1;
|
|
|
|
const handleDragStart = (e, cardId) => {
|
|
if (!isEditMode.value) {
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
|
|
draggedElement = e.target.closest(".draggable-card");
|
|
draggedIndex = cardOrder.value.indexOf(cardId);
|
|
draggedElement.classList.add("dragging");
|
|
e.dataTransfer.effectAllowed = "move";
|
|
e.dataTransfer.setData("text/html", cardId);
|
|
};
|
|
|
|
const handleDragEnd = async () => {
|
|
if (draggedElement) {
|
|
draggedElement.classList.remove("dragging");
|
|
draggedElement = null;
|
|
draggedIndex = -1;
|
|
await calculateRowHeights();
|
|
}
|
|
};
|
|
|
|
const handleDragOver = (e) => {
|
|
if (!isEditMode.value) return;
|
|
e.preventDefault();
|
|
e.dataTransfer.dropEffect = "move";
|
|
};
|
|
|
|
const handleDrop = async (e, targetCardId) => {
|
|
if (!isEditMode.value) return;
|
|
e.preventDefault();
|
|
if (draggedIndex === -1) return;
|
|
const targetIndex = cardOrder.value.indexOf(targetCardId);
|
|
if (draggedIndex !== targetIndex) {
|
|
const draggedCard = cardOrder.value.splice(draggedIndex, 1)[0];
|
|
cardOrder.value.splice(targetIndex, 0, draggedCard);
|
|
await nextTick();
|
|
await calculateRowHeights();
|
|
}
|
|
};
|
|
|
|
const isResizing = ref(false);
|
|
const resizingCard = ref(null);
|
|
const resizeDirection = ref(null);
|
|
const startX = ref(0);
|
|
const startCols = ref(0);
|
|
|
|
const cleanupResize = () => {
|
|
if (isResizing.value) {
|
|
isResizing.value = false;
|
|
resizingCard.value = null;
|
|
resizeDirection.value = null;
|
|
document.removeEventListener("mousemove", handleResize);
|
|
document.removeEventListener("mouseup", handleResizeEnd);
|
|
document.body.style.cursor = "";
|
|
document.body.style.userSelect = "";
|
|
}
|
|
};
|
|
|
|
const handleResizeStart = (e, cardId, direction) => {
|
|
if (!isEditMode.value) return;
|
|
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
if (direction !== "horizontal") return;
|
|
|
|
cleanupResize();
|
|
|
|
isResizing.value = true;
|
|
resizingCard.value = cardId;
|
|
resizeDirection.value = direction;
|
|
startX.value = e.clientX;
|
|
startCols.value = cardSizes.value[cardId].cols;
|
|
|
|
document.addEventListener("mousemove", handleResize);
|
|
document.addEventListener("mouseup", handleResizeEnd);
|
|
document.body.style.cursor = "ew-resize";
|
|
document.body.style.userSelect = "none";
|
|
};
|
|
|
|
const handleResize = async (e) => {
|
|
if (!isResizing.value || !resizingCard.value || resizeDirection.value !== "horizontal") return;
|
|
|
|
const deltaX = e.clientX - startX.value;
|
|
const containerWidth = document.querySelector(".v-row")?.offsetWidth || 1200;
|
|
const colWidth = containerWidth / 12;
|
|
const deltaColumns = Math.round(deltaX / colWidth);
|
|
const newCols = Math.max(4, Math.min(12, startCols.value + deltaColumns));
|
|
|
|
if (cardSizes.value[resizingCard.value].cols !== newCols) {
|
|
cardSizes.value[resizingCard.value].cols = newCols;
|
|
await nextTick();
|
|
await calculateRowHeights();
|
|
}
|
|
};
|
|
|
|
const handleResizeEnd = async () => {
|
|
if (!isResizing.value) return;
|
|
|
|
isResizing.value = false;
|
|
resizingCard.value = null;
|
|
resizeDirection.value = null;
|
|
startX.value = 0;
|
|
startCols.value = 0;
|
|
|
|
document.removeEventListener("mousemove", handleResize);
|
|
document.removeEventListener("mouseup", handleResizeEnd);
|
|
document.body.style.cursor = "";
|
|
document.body.style.userSelect = "";
|
|
|
|
await calculateRowHeights();
|
|
};
|
|
|
|
watch(
|
|
[cardOrder, cardSizes],
|
|
async () => {
|
|
await nextTick();
|
|
await calculateRowHeights();
|
|
},
|
|
{ deep: true }
|
|
);
|
|
|
|
onMounted(async () => {
|
|
window.addEventListener("crm-edit-mode-changed", handleEditModeChange);
|
|
window.addEventListener("add-widget-to-dashboard", handleAddWidgetToDashboard);
|
|
|
|
window.addEventListener('get-dashboard-widgets', () => {
|
|
const event = new CustomEvent('dashboard-widgets-state', {
|
|
detail: { widgets: cardOrder.value }
|
|
});
|
|
window.dispatchEvent(event);
|
|
});
|
|
|
|
await nextTick();
|
|
setTimeout(calculateRowHeights, 100);
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
window.removeEventListener("crm-edit-mode-changed", handleEditModeChange);
|
|
window.removeEventListener("add-widget-to-dashboard", handleAddWidgetToDashboard);
|
|
cleanupResize();
|
|
});
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<VRow class="match-height" :class="{ 'edit-mode-active': isEditMode, 'drag-over': false }"
|
|
@dragover="handleDragOverFromSidebar" @dragleave="handleDragLeaveFromSidebar" @drop="handleDropFromSidebar">
|
|
|
|
<VCol v-if="isEditMode" cols="12" class="pb-0">
|
|
<div class="edit-mode-banner">
|
|
<div class="banner-background">
|
|
<div class="banner-glow"></div>
|
|
<div class="banner-particles"></div>
|
|
</div>
|
|
|
|
<div class="banner-content">
|
|
<div class="banner-header">
|
|
<div class="banner-icon">
|
|
<VIcon icon="tabler-edit" size="24" class="edit-icon" />
|
|
<div class="icon-pulse"></div>
|
|
</div>
|
|
<div class="banner-text">
|
|
<h3 class="banner-title">✨ Edit Mode Enabled</h3>
|
|
<p class="banner-subtitle">
|
|
Customize your dashboard with drag & drop, resize, and delete
|
|
features
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="banner-actions">
|
|
<VBtn variant="elevated" size="default" color="white" @click="restoreAllCards" class="restore-btn-enhanced">
|
|
<VIcon icon="tabler-reload" size="18" class="me-2" />
|
|
Restore All Cards
|
|
</VBtn>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="banner-decorations">
|
|
<div class="decoration decoration-1"></div>
|
|
<div class="decoration decoration-2"></div>
|
|
<div class="decoration decoration-3"></div>
|
|
</div>
|
|
</div>
|
|
</VCol>
|
|
|
|
<template v-for="cardId in cardOrder" :key="cardId">
|
|
<VCol :cols="12" :md="cardSizes[cardId].cols" class="draggable-card" :class="{
|
|
resizing: isResizing && resizingCard === cardId,
|
|
'edit-mode': isEditMode,
|
|
'view-mode': !isEditMode
|
|
}" :draggable="isEditMode" :data-card-id="cardId" @dragstart="handleDragStart($event, cardId)"
|
|
@dragend="handleDragEnd" @dragover.prevent.stop="handleDragOver($event)"
|
|
@drop.prevent.stop="handleDrop($event, cardId)">
|
|
|
|
<div class="card-wrapper" :style="{
|
|
height: getCardHeight(cardId),
|
|
}">
|
|
<div v-if="isEditMode" class="drag-handle" :title="'Move ' + cardId">
|
|
<v-icon size="18">mdi-drag-horizontal-variant</v-icon>
|
|
</div>
|
|
|
|
<VBtn v-if="isEditMode" variant="text" size="small" color="error" class="delete-btn"
|
|
:title="`Delete ${cardId}`" @click.stop="handleDeleteCard(cardId)">
|
|
<VIcon icon="tabler-trash-x" size="16" class="me-1" />
|
|
Delete
|
|
</VBtn>
|
|
|
|
<div v-if="isEditMode" class="resize-handle resize-horizontal" :title="'Resize ' + cardId"
|
|
@mousedown="handleResizeStart($event, cardId, 'horizontal')">
|
|
<v-icon size="16">mdi-resize</v-icon>
|
|
</div>
|
|
|
|
<component :is="cardComponents[cardId].component" v-bind="cardComponents[cardId].props"
|
|
class="card-content" />
|
|
</div>
|
|
</VCol>
|
|
</template>
|
|
</VRow>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.draggable-card {
|
|
position: relative;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.draggable-card.edit-mode {
|
|
cursor: move;
|
|
}
|
|
|
|
.draggable-card.edit-mode:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.draggable-card.edit-mode .card-wrapper {
|
|
border: 2px dashed rgba(var(--v-theme-primary), 0.3);
|
|
background: rgba(var(--v-theme-primary), 0.02);
|
|
}
|
|
|
|
.draggable-card.edit-mode:hover .card-wrapper {
|
|
border-color: rgba(var(--v-theme-primary), 0.6);
|
|
background: rgba(var(--v-theme-primary), 0.05);
|
|
}
|
|
|
|
.draggable-card.view-mode {
|
|
cursor: default;
|
|
}
|
|
|
|
.draggable-card.view-mode .card-wrapper {
|
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.dragging {
|
|
opacity: 0.5;
|
|
transform: rotate(5deg) scale(1.05);
|
|
z-index: 1000;
|
|
}
|
|
|
|
.resizing {
|
|
transition: none !important;
|
|
}
|
|
|
|
.card-wrapper {
|
|
position: relative;
|
|
height: 100%;
|
|
overflow: hidden;
|
|
border-radius: 8px;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.card-content {
|
|
height: 100%;
|
|
overflow: auto;
|
|
}
|
|
|
|
.edit-mode-banner {
|
|
position: relative;
|
|
margin: 0 0 2rem 0;
|
|
border-radius: 20px;
|
|
overflow: hidden;
|
|
background: linear-gradient(135deg,
|
|
rgba(var(--v-theme-primary), 0.9) 0%,
|
|
rgba(var(--v-theme-secondary), 0.8) 50%,
|
|
rgba(var(--v-theme-accent), 0.9) 100%);
|
|
box-shadow: 0 8px 32px rgba(var(--v-theme-primary), 0.3),
|
|
0 2px 8px rgba(0, 0, 0, 0.1);
|
|
animation: bannerSlideIn 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
|
backdrop-filter: blur(20px);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
.banner-background {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.banner-glow {
|
|
position: absolute;
|
|
top: -50%;
|
|
left: -50%;
|
|
width: 200%;
|
|
height: 200%;
|
|
background: radial-gradient(circle,
|
|
rgba(255, 255, 255, 0.1) 0%,
|
|
transparent 70%);
|
|
animation: bannerGlow 4s ease-in-out infinite;
|
|
}
|
|
|
|
.banner-particles {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background-image: radial-gradient(2px 2px at 20% 30%,
|
|
rgba(255, 255, 255, 0.3),
|
|
transparent),
|
|
radial-gradient(1px 1px at 60% 70%, rgba(255, 255, 255, 0.2), transparent),
|
|
radial-gradient(1px 1px at 90% 40%, rgba(255, 255, 255, 0.3), transparent),
|
|
radial-gradient(2px 2px at 40% 80%, rgba(255, 255, 255, 0.2), transparent);
|
|
background-size: 200px 200px, 150px 150px, 100px 100px, 250px 250px;
|
|
animation: particlesFloat 15s linear infinite;
|
|
}
|
|
|
|
.banner-content {
|
|
position: relative;
|
|
z-index: 2;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 1.5rem 2rem;
|
|
gap: 1.5rem;
|
|
}
|
|
|
|
.banner-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
flex: 1;
|
|
}
|
|
|
|
.banner-icon {
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 48px;
|
|
height: 48px;
|
|
background: rgba(255, 255, 255, 0.2);
|
|
border-radius: 12px;
|
|
backdrop-filter: blur(10px);
|
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
}
|
|
|
|
.edit-icon {
|
|
color: white;
|
|
z-index: 2;
|
|
}
|
|
|
|
.icon-pulse {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
border-radius: 12px;
|
|
background: rgba(255, 255, 255, 0.3);
|
|
animation: iconPulse 2s ease-in-out infinite;
|
|
}
|
|
|
|
.banner-text {
|
|
flex: 1;
|
|
}
|
|
|
|
.banner-title {
|
|
font-size: 1.4rem;
|
|
font-weight: 700;
|
|
color: white;
|
|
margin: 0 0 0.25rem 0;
|
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
|
letter-spacing: -0.5px;
|
|
}
|
|
|
|
.banner-subtitle {
|
|
font-size: 0.9rem;
|
|
color: rgba(255, 255, 255, 0.85);
|
|
margin: 0;
|
|
line-height: 1.4;
|
|
font-weight: 400;
|
|
}
|
|
|
|
.banner-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.restore-btn-enhanced {
|
|
background: rgba(255, 255, 255, 0.95) !important;
|
|
color: rgba(var(--v-theme-primary), 0.9) !important;
|
|
border-radius: 12px !important;
|
|
padding: 12px 20px !important;
|
|
font-weight: 600 !important;
|
|
text-transform: none !important;
|
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.05) !important;
|
|
border: 1px solid rgba(255, 255, 255, 0.3) !important;
|
|
backdrop-filter: blur(10px);
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
|
height: auto !important;
|
|
min-height: 44px !important;
|
|
}
|
|
|
|
.restore-btn-enhanced:hover {
|
|
background: white !important;
|
|
transform: translateY(-2px) scale(1.02);
|
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15), 0 4px 8px rgba(0, 0, 0, 0.1) !important;
|
|
}
|
|
|
|
.restore-btn-enhanced:active {
|
|
transform: translateY(-1px) scale(1.01);
|
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.05) !important;
|
|
}
|
|
|
|
.restore-btn-enhanced .v-icon {
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.restore-btn-enhanced:hover .v-icon {
|
|
transform: rotate(180deg);
|
|
}
|
|
|
|
.banner-decorations {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
pointer-events: none;
|
|
z-index: 1;
|
|
}
|
|
|
|
.decoration {
|
|
position: absolute;
|
|
border-radius: 50%;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
}
|
|
|
|
.decoration-1 {
|
|
width: 80px;
|
|
height: 80px;
|
|
top: -40px;
|
|
right: 10%;
|
|
animation: decorationFloat1 8s ease-in-out infinite;
|
|
}
|
|
|
|
.decoration-2 {
|
|
width: 60px;
|
|
height: 60px;
|
|
bottom: -30px;
|
|
left: 15%;
|
|
animation: decorationFloat2 6s ease-in-out infinite reverse;
|
|
}
|
|
|
|
.decoration-3 {
|
|
width: 40px;
|
|
height: 40px;
|
|
top: 50%;
|
|
right: 5%;
|
|
animation: decorationFloat3 10s ease-in-out infinite;
|
|
}
|
|
|
|
.drag-handle {
|
|
position: absolute;
|
|
top: 8px;
|
|
right: 8px;
|
|
z-index: 20;
|
|
cursor: grab;
|
|
padding: 6px;
|
|
border-radius: 50%;
|
|
background: rgba(var(--v-theme-primary), 0.9);
|
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
opacity: 0;
|
|
transition: all 0.2s ease;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
color: white;
|
|
}
|
|
|
|
.drag-handle:hover {
|
|
background: rgba(var(--v-theme-primary), 1);
|
|
transform: scale(1.1);
|
|
}
|
|
|
|
.drag-handle:active {
|
|
cursor: grabbing;
|
|
transform: scale(0.95);
|
|
}
|
|
|
|
.delete-btn {
|
|
position: absolute;
|
|
top: 8px;
|
|
left: 8px;
|
|
z-index: 5;
|
|
opacity: 1 !important;
|
|
background: rgba(255, 82, 82, 0.9) !important;
|
|
color: white !important;
|
|
border-radius: 6px !important;
|
|
padding: 4px 8px !important;
|
|
font-size: 12px !important;
|
|
min-width: auto !important;
|
|
height: 28px !important;
|
|
box-shadow: 0 2px 6px rgba(255, 82, 82, 0.3);
|
|
transition: all 0.2s ease;
|
|
backdrop-filter: blur(10px);
|
|
}
|
|
|
|
.delete-btn:hover {
|
|
background: rgba(220, 38, 38, 1) !important;
|
|
transform: scale(1.05);
|
|
box-shadow: 0 4px 12px rgba(220, 38, 38, 0.4);
|
|
}
|
|
|
|
.delete-btn:active {
|
|
transform: scale(0.95);
|
|
}
|
|
|
|
.resize-handle {
|
|
position: absolute;
|
|
z-index: 15;
|
|
background: rgba(var(--v-theme-secondary), 0.9);
|
|
border: 1px solid rgba(0, 0, 0, 0.2);
|
|
border-radius: 4px;
|
|
opacity: 0;
|
|
transition: all 0.2s ease;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: white;
|
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.resize-horizontal {
|
|
right: -6px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
width: 14px;
|
|
height: 50px;
|
|
cursor: ew-resize;
|
|
}
|
|
|
|
.resize-handle:hover {
|
|
background: rgba(var(--v-theme-secondary), 1);
|
|
transform: scale(1.1);
|
|
}
|
|
|
|
.resize-horizontal:hover {
|
|
transform: translateY(-50%) scale(1.1);
|
|
}
|
|
|
|
.edit-mode:hover .drag-handle,
|
|
.edit-mode:hover .resize-handle {
|
|
opacity: 1;
|
|
}
|
|
|
|
.draggable-card.drag-over .card-wrapper {
|
|
border: 2px dashed rgba(var(--v-theme-success), 0.5) !important;
|
|
background: rgba(var(--v-theme-success), 0.05) !important;
|
|
}
|
|
|
|
.edit-mode-active {
|
|
padding: 1rem;
|
|
background: rgba(var(--v-theme-surface), 0.5);
|
|
border-radius: 12px;
|
|
border: 1px dashed rgba(var(--v-theme-primary), 0.2);
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.banner-content {
|
|
flex-direction: column;
|
|
text-align: center;
|
|
gap: 1rem;
|
|
padding: 1.25rem 1rem;
|
|
}
|
|
|
|
.banner-header {
|
|
flex-direction: column;
|
|
text-align: center;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.banner-title {
|
|
font-size: 1.2rem;
|
|
}
|
|
|
|
.banner-subtitle {
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.restore-btn-enhanced {
|
|
padding: 10px 16px !important;
|
|
font-size: 0.9rem !important;
|
|
}
|
|
|
|
.edit-mode .drag-handle,
|
|
.edit-mode .resize-handle {
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.delete-btn {
|
|
font-size: 11px !important;
|
|
padding: 3px 6px !important;
|
|
height: 24px !important;
|
|
}
|
|
|
|
.resize-handle {
|
|
background: rgba(var(--v-theme-secondary), 0.8);
|
|
}
|
|
}
|
|
|
|
.resizing * {
|
|
user-select: none !important;
|
|
pointer-events: none !important;
|
|
}
|
|
</style> |