feat: enhance chat functionality with new conversation and meeting room features
- Added support for creating and managing group conversations and meeting rooms, allowing users to initiate discussions with multiple participants. - Implemented a modal for creating new conversations, including options for individual, group, and meeting room types. - Enhanced the chat list component to filter and display conversations based on type, improving user navigation. - Introduced admin functionalities for meeting rooms, enabling user management and role assignments within the chat interface. - Updated backend schema and API to accommodate new conversation types and related operations, ensuring robust data handling.
This commit is contained in:
@@ -43,6 +43,102 @@
|
||||
let dragStart = $state({ x: 0, y: 0 });
|
||||
let isAnimating = $state(false);
|
||||
|
||||
// Tamanho da janela (redimensionável)
|
||||
const MIN_WIDTH = 300;
|
||||
const MAX_WIDTH = 1200;
|
||||
const MIN_HEIGHT = 400;
|
||||
const MAX_HEIGHT = 900;
|
||||
const DEFAULT_WIDTH = 440;
|
||||
const DEFAULT_HEIGHT = 680;
|
||||
|
||||
// Carregar tamanho salvo do localStorage ou usar padrão
|
||||
function getSavedSize() {
|
||||
if (typeof window === 'undefined') return { width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT };
|
||||
const saved = localStorage.getItem('chat-window-size');
|
||||
if (saved) {
|
||||
try {
|
||||
const parsed = JSON.parse(saved);
|
||||
return {
|
||||
width: Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, parsed.width || DEFAULT_WIDTH)),
|
||||
height: Math.max(MIN_HEIGHT, Math.min(MAX_HEIGHT, parsed.height || DEFAULT_HEIGHT))
|
||||
};
|
||||
} catch {
|
||||
return { width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT };
|
||||
}
|
||||
}
|
||||
return { width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT };
|
||||
}
|
||||
|
||||
let windowSize = $state(getSavedSize());
|
||||
|
||||
// Salvar tamanho no localStorage
|
||||
function saveSize() {
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem('chat-window-size', JSON.stringify(windowSize));
|
||||
}
|
||||
}
|
||||
|
||||
// Redimensionamento
|
||||
let isResizing = $state(false);
|
||||
let resizeStart = $state({ x: 0, y: 0, width: 0, height: 0 });
|
||||
let resizeDirection = $state<string | null>(null); // 'n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'
|
||||
|
||||
function handleResizeStart(e: MouseEvent, direction: string) {
|
||||
if (e.button !== 0) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
isResizing = true;
|
||||
resizeDirection = direction;
|
||||
resizeStart = {
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
width: windowSize.width,
|
||||
height: windowSize.height
|
||||
};
|
||||
document.body.classList.add('resizing');
|
||||
}
|
||||
|
||||
function handleResizeMove(e: MouseEvent) {
|
||||
if (!isResizing || !resizeDirection) return;
|
||||
|
||||
const deltaX = e.clientX - resizeStart.x;
|
||||
const deltaY = e.clientY - resizeStart.y;
|
||||
|
||||
let newWidth = resizeStart.width;
|
||||
let newHeight = resizeStart.height;
|
||||
let newX = position.x;
|
||||
let newY = position.y;
|
||||
|
||||
// Redimensionar baseado na direção
|
||||
if (resizeDirection.includes('e')) {
|
||||
newWidth = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, resizeStart.width + deltaX));
|
||||
}
|
||||
if (resizeDirection.includes('w')) {
|
||||
newWidth = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, resizeStart.width - deltaX));
|
||||
newX = position.x + (resizeStart.width - newWidth);
|
||||
}
|
||||
if (resizeDirection.includes('s')) {
|
||||
newHeight = Math.max(MIN_HEIGHT, Math.min(MAX_HEIGHT, resizeStart.height + deltaY));
|
||||
}
|
||||
if (resizeDirection.includes('n')) {
|
||||
newHeight = Math.max(MIN_HEIGHT, Math.min(MAX_HEIGHT, resizeStart.height - deltaY));
|
||||
newY = position.y + (resizeStart.height - newHeight);
|
||||
}
|
||||
|
||||
windowSize = { width: newWidth, height: newHeight };
|
||||
position = { x: newX, y: newY };
|
||||
}
|
||||
|
||||
function handleResizeEnd() {
|
||||
if (isResizing) {
|
||||
isResizing = false;
|
||||
resizeDirection = null;
|
||||
document.body.classList.remove('resizing');
|
||||
saveSize();
|
||||
ajustarPosicao();
|
||||
}
|
||||
}
|
||||
|
||||
// Sincronizar com stores
|
||||
$effect(() => {
|
||||
isOpen = $chatAberto;
|
||||
@@ -89,14 +185,19 @@
|
||||
}
|
||||
|
||||
function handleMouseMove(e: MouseEvent) {
|
||||
if (isResizing) {
|
||||
handleResizeMove(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isDragging) return;
|
||||
|
||||
const newX = e.clientX - dragStart.x;
|
||||
const newY = e.clientY - dragStart.y;
|
||||
|
||||
// Dimensões do widget
|
||||
const widgetWidth = isOpen && !isMinimized ? 440 : 72;
|
||||
const widgetHeight = isOpen && !isMinimized ? 680 : 72;
|
||||
const widgetWidth = isOpen && !isMinimized ? windowSize.width : 72;
|
||||
const widgetHeight = isOpen && !isMinimized ? windowSize.height : 72;
|
||||
|
||||
// Limites da tela com margem de segurança
|
||||
const minX = -(widgetWidth - 100); // Permitir até 100px visíveis
|
||||
@@ -117,14 +218,15 @@
|
||||
// Garantir que está dentro dos limites ao soltar
|
||||
ajustarPosicao();
|
||||
}
|
||||
handleResizeEnd();
|
||||
}
|
||||
|
||||
function ajustarPosicao() {
|
||||
isAnimating = true;
|
||||
|
||||
// Dimensões do widget
|
||||
const widgetWidth = isOpen && !isMinimized ? 440 : 72;
|
||||
const widgetHeight = isOpen && !isMinimized ? 680 : 72;
|
||||
const widgetWidth = isOpen && !isMinimized ? windowSize.width : 72;
|
||||
const widgetHeight = isOpen && !isMinimized ? windowSize.height : 72;
|
||||
|
||||
// Verificar se está fora dos limites
|
||||
let newX = position.x;
|
||||
@@ -243,10 +345,10 @@
|
||||
class="fixed flex flex-col overflow-hidden backdrop-blur-2xl"
|
||||
style="
|
||||
z-index: 99999 !important;
|
||||
bottom: {position.y === 0 ? '1.5rem' : `${window.innerHeight - position.y - 680}px`};
|
||||
right: {position.x === 0 ? '1.5rem' : `${window.innerWidth - position.x - 440}px`};
|
||||
width: 440px;
|
||||
height: 680px;
|
||||
bottom: {position.y === 0 ? '1.5rem' : `${window.innerHeight - position.y - windowSize.height}px`};
|
||||
right: {position.x === 0 ? '1.5rem' : `${window.innerWidth - position.x - windowSize.width}px`};
|
||||
width: {windowSize.width}px;
|
||||
height: {windowSize.height}px;
|
||||
max-width: calc(100vw - 3rem);
|
||||
max-height: calc(100vh - 3rem);
|
||||
position: fixed !important;
|
||||
@@ -335,6 +437,30 @@
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Botão maximizar MODERNO -->
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center justify-center w-10 h-10 rounded-xl transition-all duration-300 group relative overflow-hidden"
|
||||
style="background: rgba(255,255,255,0.15); backdrop-filter: blur(10px); border: 1px solid rgba(255,255,255,0.2);"
|
||||
onclick={handleMaximize}
|
||||
aria-label="Maximizar"
|
||||
>
|
||||
<div class="absolute inset-0 bg-white/0 group-hover:bg-white/20 transition-colors duration-300"></div>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="w-5 h-5 relative z-10 group-hover:scale-110 transition-transform duration-300"
|
||||
style="filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));"
|
||||
>
|
||||
<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Botão fechar MODERNO -->
|
||||
<button
|
||||
type="button"
|
||||
@@ -363,12 +489,59 @@
|
||||
</div>
|
||||
|
||||
<!-- Conteúdo -->
|
||||
<div class="flex-1 overflow-hidden">
|
||||
<div class="flex-1 overflow-hidden relative">
|
||||
{#if !activeConversation}
|
||||
<ChatList />
|
||||
{:else}
|
||||
<ChatWindow conversaId={activeConversation} />
|
||||
{/if}
|
||||
|
||||
<!-- Resize Handles -->
|
||||
<!-- Top -->
|
||||
<div
|
||||
class="absolute top-0 left-0 right-0 h-2 cursor-ns-resize hover:bg-primary/20 transition-colors z-50"
|
||||
onmousedown={(e) => handleResizeStart(e, 'n')}
|
||||
style="border-radius: 24px 24px 0 0;"
|
||||
></div>
|
||||
<!-- Bottom -->
|
||||
<div
|
||||
class="absolute bottom-0 left-0 right-0 h-2 cursor-ns-resize hover:bg-primary/20 transition-colors z-50"
|
||||
onmousedown={(e) => handleResizeStart(e, 's')}
|
||||
style="border-radius: 0 0 24px 24px;"
|
||||
></div>
|
||||
<!-- Left -->
|
||||
<div
|
||||
class="absolute top-0 bottom-0 left-0 w-2 cursor-ew-resize hover:bg-primary/20 transition-colors z-50"
|
||||
onmousedown={(e) => handleResizeStart(e, 'w')}
|
||||
style="border-radius: 24px 0 0 24px;"
|
||||
></div>
|
||||
<!-- Right -->
|
||||
<div
|
||||
class="absolute top-0 bottom-0 right-0 w-2 cursor-ew-resize hover:bg-primary/20 transition-colors z-50"
|
||||
onmousedown={(e) => handleResizeStart(e, 'e')}
|
||||
style="border-radius: 0 24px 24px 0;"
|
||||
></div>
|
||||
<!-- Corners -->
|
||||
<div
|
||||
class="absolute top-0 left-0 w-4 h-4 cursor-nwse-resize hover:bg-primary/20 transition-colors z-50"
|
||||
onmousedown={(e) => handleResizeStart(e, 'nw')}
|
||||
style="border-radius: 24px 0 0 0;"
|
||||
></div>
|
||||
<div
|
||||
class="absolute top-0 right-0 w-4 h-4 cursor-nesw-resize hover:bg-primary/20 transition-colors z-50"
|
||||
onmousedown={(e) => handleResizeStart(e, 'ne')}
|
||||
style="border-radius: 0 24px 0 0;"
|
||||
></div>
|
||||
<div
|
||||
class="absolute bottom-0 left-0 w-4 h-4 cursor-nesw-resize hover:bg-primary/20 transition-colors z-50"
|
||||
onmousedown={(e) => handleResizeStart(e, 'sw')}
|
||||
style="border-radius: 0 0 0 24px;"
|
||||
></div>
|
||||
<div
|
||||
class="absolute bottom-0 right-0 w-4 h-4 cursor-nwse-resize hover:bg-primary/20 transition-colors z-50"
|
||||
onmousedown={(e) => handleResizeStart(e, 'se')}
|
||||
style="border-radius: 0 0 24px 0;"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user