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:
2025-11-05 07:20:37 -03:00
parent aa3e3470cd
commit 8ca737c62f
9 changed files with 1665 additions and 212 deletions

View File

@@ -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}