Files
panel/resources/js/pages/dashboards/crm.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>