|
|
@@ -1,40 +1,77 @@
|
|
|
<script>
|
|
|
import { goto } from '$app/navigation';
|
|
|
+ import { onMount } from 'svelte';
|
|
|
import { browser } from '$app/environment';
|
|
|
|
|
|
let token = null;
|
|
|
+ let company = null;
|
|
|
+ let flag = null;
|
|
|
|
|
|
if (browser) {
|
|
|
token = localStorage.getItem('token');
|
|
|
+ company = Number(localStorage.getItem('company'));
|
|
|
+ flag = localStorage.getItem('flag');
|
|
|
}
|
|
|
|
|
|
- // Ícones (substitua os caminhos pelas suas imagens)
|
|
|
- //import coffeeIcon from '$lib/assets/coffee.svg';
|
|
|
- //import clockIcon from '$lib/assets/clock.svg';
|
|
|
- //import creditCardIcon from '$lib/assets/credit-card.svg';
|
|
|
- //import plusIcon from '$lib/assets/plus.svg';
|
|
|
- //import trashIcon from '$lib/assets/trash.svg';
|
|
|
- //import printerIcon from '$lib/assets/printer.svg';
|
|
|
-
|
|
|
- let order;
|
|
|
-
|
|
|
- // Dados simulados — substitua depois pela sua API
|
|
|
- let tables = [
|
|
|
- { id: 1, status: 'FREE', startTime: null },
|
|
|
- { id: 2, status: 'OCCUPIED', startTime: '2025-07-19T10:00:00' },
|
|
|
- { id: 3, status: 'ALERT', startTime: '2025-07-19T08:45:00' }
|
|
|
- ];
|
|
|
-
|
|
|
- let orders = {
|
|
|
- 2: { items: [{}, {}], totalAmount: 42.5 },
|
|
|
- 3: { items: [{}], totalAmount: 19.99 }
|
|
|
+ const statusMap = {
|
|
|
+ 1: 'FREE',
|
|
|
+ 2: 'OCCUPIED',
|
|
|
+ 3: 'ALERT'
|
|
|
};
|
|
|
|
|
|
- function getTableOrder(tableId) {
|
|
|
+ const statusToApiMap = {
|
|
|
+ FREE: 'Livre',
|
|
|
+ OCCUPIED: 'Ocupado',
|
|
|
+ ALERT: 'Alerta'
|
|
|
+ };
|
|
|
+
|
|
|
+ let tables = [];
|
|
|
+
|
|
|
+ let orders = {};
|
|
|
+
|
|
|
+ let newTableNumber = '';
|
|
|
+ let showConfirmDelete = false;
|
|
|
+ let tableToDelete = null;
|
|
|
+
|
|
|
+ const getTableOrder = (tableId) => {
|
|
|
return orders[tableId];
|
|
|
- }
|
|
|
+ };
|
|
|
+
|
|
|
+ const updateTableStatus = async (tableNumber, newStatus) => {
|
|
|
+ const requestOptions = {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ Authorization: `Bearer ${token}`
|
|
|
+ },
|
|
|
+ redirect: 'follow',
|
|
|
+ body: JSON.stringify({
|
|
|
+ table_number: tableNumber,
|
|
|
+ company_id: company,
|
|
|
+ status_status: statusToApiMap[newStatus]
|
|
|
+ })
|
|
|
+ };
|
|
|
|
|
|
- function getStatusColor(status) {
|
|
|
+ const response = await fetch('https://dev2.mixtech.dev.br/table/update', requestOptions);
|
|
|
+ const result = await response.json();
|
|
|
+ if (result.status !== 'ok') {
|
|
|
+ throw new Error('Falha ao atualizar status: ' + result.msg);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const startOrder = async (tableId, tableNumber) => {
|
|
|
+ await updateTableStatus(tableNumber, 'OCCUPIED');
|
|
|
+ tables = tables.map((t) => {
|
|
|
+ if (t.id === tableId) {
|
|
|
+ return { ...t, status: 'OCCUPIED', startTime: new Date().toISOString() };
|
|
|
+ }
|
|
|
+ return t;
|
|
|
+ });
|
|
|
+ orders = { ...orders, [tableId]: { items: [], totalAmount: 0 } };
|
|
|
+ console.log(`Order started for table ${tableId}`);
|
|
|
+ };
|
|
|
+
|
|
|
+ const getStatusColor = (status) => {
|
|
|
switch (status) {
|
|
|
case 'FREE':
|
|
|
return 'bg-green-600 hover:bg-green-700';
|
|
|
@@ -45,9 +82,9 @@
|
|
|
default:
|
|
|
return 'bg-gray-700 hover:bg-gray-800';
|
|
|
}
|
|
|
- }
|
|
|
+ };
|
|
|
|
|
|
- function getStatusText(status) {
|
|
|
+ const getStatusText = (status) => {
|
|
|
switch (status) {
|
|
|
case 'FREE':
|
|
|
return 'Livre';
|
|
|
@@ -58,115 +95,225 @@
|
|
|
default:
|
|
|
return 'Desconhecido';
|
|
|
}
|
|
|
- }
|
|
|
+ };
|
|
|
|
|
|
- function getOrderTime(startTime) {
|
|
|
+ const getOrderTime = (startTime) => {
|
|
|
if (!startTime) return '';
|
|
|
+
|
|
|
const startDateTime = new Date(startTime);
|
|
|
const now = new Date();
|
|
|
const diff = Math.floor((now.getTime() - startDateTime.getTime()) / 1000 / 60);
|
|
|
- if (diff < 60) return `${diff} min`;
|
|
|
- const hours = Math.floor(diff / 60);
|
|
|
- const mins = diff % 60;
|
|
|
- return `${hours}h ${mins}m`;
|
|
|
- }
|
|
|
|
|
|
- async function handleTableClick(tableId) {
|
|
|
+ if (diff < 60) {
|
|
|
+ return `${diff} min`;
|
|
|
+ } else {
|
|
|
+ const hours = Math.floor(diff / 60);
|
|
|
+ const mins = diff % 60;
|
|
|
+ return `${hours}h ${mins}m`;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleTableClick = async (tableId, tableNumber) => {
|
|
|
const table = tables.find((t) => t.id === tableId);
|
|
|
if (!table) return;
|
|
|
|
|
|
if (table.status === 'FREE') {
|
|
|
- // 🔁 Aqui você pode fazer o fetch para iniciar o pedido
|
|
|
- // await fetch('/start-order', { method: 'POST', body: JSON.stringify({ tableId }) });
|
|
|
+ await startOrder(tableId, tableNumber);
|
|
|
+ } else {
|
|
|
+ await goto(`/pos/${tableId}`);
|
|
|
}
|
|
|
+ };
|
|
|
|
|
|
- goto(`/pos/${tableId}`);
|
|
|
- }
|
|
|
+ const openDeleteConfirm = (tableId, tableNumber) => {
|
|
|
+ tableToDelete = { id: tableId, number: tableNumber };
|
|
|
+ showConfirmDelete = true;
|
|
|
+ };
|
|
|
|
|
|
- function printKitchenOrder(tableId) {
|
|
|
- // 🔁 Substitua com sua lógica de impressão
|
|
|
- alert(`Imprimir pedido da mesa ${tableId}`);
|
|
|
- }
|
|
|
+ const cancelDelete = () => {
|
|
|
+ showConfirmDelete = false;
|
|
|
+ tableToDelete = null;
|
|
|
+ };
|
|
|
|
|
|
- function cancelOrder(tableId) {
|
|
|
- // 🔁 Substitua com sua lógica de cancelamento
|
|
|
- alert(`Cancelar pedido da mesa ${tableId}`);
|
|
|
- }
|
|
|
+ const deleteTable = async () => {
|
|
|
+ if (!tableToDelete) return;
|
|
|
+
|
|
|
+ const requestOptions = {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ Authorization: `Bearer ${token}`
|
|
|
+ },
|
|
|
+ redirect: 'follow',
|
|
|
+ body: JSON.stringify({
|
|
|
+ table_number: tableToDelete.number,
|
|
|
+ company_id: company
|
|
|
+ })
|
|
|
+ };
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch('https://dev2.mixtech.dev.br/table/delete', requestOptions);
|
|
|
+ const result = await response.json();
|
|
|
+ if (result.status === 'ok') {
|
|
|
+ tables = tables.filter((t) => t.id !== tableToDelete.id);
|
|
|
+ const newOrders = { ...orders };
|
|
|
+ delete newOrders[tableToDelete.id];
|
|
|
+ orders = newOrders;
|
|
|
+ showConfirmDelete = false;
|
|
|
+ tableToDelete = null;
|
|
|
+ } else {
|
|
|
+ console.error('Erro ao excluir mesa:', result.msg);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error(error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const fetchTables = async () => {
|
|
|
+ const requestOptions = {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ Authorization: `Bearer ${token}`
|
|
|
+ },
|
|
|
+ redirect: 'follow',
|
|
|
+ body: JSON.stringify({ company_id: company })
|
|
|
+ };
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch('https://dev2.mixtech.dev.br/table/get', requestOptions);
|
|
|
+ const result = await response.json();
|
|
|
+ if (result.status === 'ok') {
|
|
|
+ tables = result.data.map((d) => ({
|
|
|
+ id: d.table_id,
|
|
|
+ number: d.table_number,
|
|
|
+ status: statusMap[d.status_id] || 'Desconhecido',
|
|
|
+ startTime: statusMap[d.status_id] === 'FREE' ? null : new Date().toISOString()
|
|
|
+ }));
|
|
|
+ tables.forEach((t) => {
|
|
|
+ if (t.status !== 'FREE' && !orders[t.id]) {
|
|
|
+ orders[t.id] = { items: [], totalAmount: 0 };
|
|
|
+ }
|
|
|
+ });
|
|
|
+ orders = { ...orders };
|
|
|
+ } else {
|
|
|
+ console.error('Erro ao buscar mesas:', result.msg);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error(error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const createTable = async () => {
|
|
|
+ if (!newTableNumber) return;
|
|
|
+
|
|
|
+ const payload = {
|
|
|
+ company_id: company,
|
|
|
+ table_number: newTableNumber,
|
|
|
+ status_id: 1
|
|
|
+ };
|
|
|
+
|
|
|
+ const requestOptions = {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ Authorization: `Bearer ${token}`
|
|
|
+ },
|
|
|
+ redirect: 'follow',
|
|
|
+ body: JSON.stringify(payload)
|
|
|
+ };
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch('https://dev2.mixtech.dev.br/table/create', requestOptions);
|
|
|
+ const result = await response.json();
|
|
|
+ if (result.status === 'ok') {
|
|
|
+ await fetchTables();
|
|
|
+ newTableNumber = '';
|
|
|
+ } else {
|
|
|
+ console.error('Erro ao criar mesa:', result.msg);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error(error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ onMount(() => {
|
|
|
+ fetchTables();
|
|
|
+ });
|
|
|
</script>
|
|
|
|
|
|
<div class="container mx-auto">
|
|
|
<div class="flex flex-col">
|
|
|
<h1 class="mb-6 text-2xl font-bold">Mesas</h1>
|
|
|
|
|
|
+ {#if flag === 'admin'}
|
|
|
+ <div class="mb-4 flex">
|
|
|
+ <input
|
|
|
+ bind:value={newTableNumber}
|
|
|
+ placeholder="Número da nova mesa"
|
|
|
+ class="flex-grow rounded-l-md bg-gray-700 px-4 py-2 text-white focus:outline-none"
|
|
|
+ />
|
|
|
+ <button
|
|
|
+ on:click={createTable}
|
|
|
+ class="rounded-r-md bg-blue-600 px-4 py-2 text-white transition-colors hover:bg-blue-700"
|
|
|
+ >
|
|
|
+ Criar Mesa
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ {/if}
|
|
|
+
|
|
|
<div class="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">
|
|
|
{#each tables as table}
|
|
|
- {(order = getTableOrder(table.id))}
|
|
|
+ {@const order = getTableOrder(table.id)}
|
|
|
|
|
|
<div
|
|
|
class="transform overflow-hidden rounded-lg bg-gray-800 shadow-md transition-transform duration-200 hover:scale-105"
|
|
|
>
|
|
|
<div class={`flex items-center justify-between p-4 ${getStatusColor(table.status)}`}>
|
|
|
- <div class="flex items-center">
|
|
|
- <!--<img src={coffeeIcon} alt="Mesa" class="mr-2 h-5 w-5" />-->
|
|
|
- <span class="font-medium">Mesa {table.id}</span>
|
|
|
- </div>
|
|
|
+ <span class="font-medium">Mesa {table.number}</span>
|
|
|
<span class="text-sm font-medium">{getStatusText(table.status)}</span>
|
|
|
</div>
|
|
|
|
|
|
<div class="p-4">
|
|
|
{#if table.status !== 'FREE' && order}
|
|
|
<div class="space-y-3">
|
|
|
- <div class="flex justify-between text-sm">
|
|
|
- <div class="flex items-center text-gray-400">
|
|
|
- <!--<img src={clockIcon} alt="Relógio" class="mr-1 h-4 w-4" />-->
|
|
|
- <span>{getOrderTime(table.startTime)}</span>
|
|
|
- </div>
|
|
|
+ <!-- <div class="flex justify-between text-sm">
|
|
|
+ <span class="text-gray-400">{getOrderTime(table.startTime)}</span>
|
|
|
<div class="font-medium">
|
|
|
{order.items.length}
|
|
|
{order.items.length === 1 ? 'item' : 'itens'}
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ </div> -->
|
|
|
|
|
|
- <div class="flex justify-between text-sm">
|
|
|
+ <!-- <div class="flex justify-between text-sm">
|
|
|
<span class="text-gray-400">Total:</span>
|
|
|
- <span class="font-medium">R$ {order.totalAmount.toFixed(2)}</span>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="mt-2 grid grid-cols-2 gap-2">
|
|
|
- <button
|
|
|
- on:click|stopPropagation={() => printKitchenOrder(table.id)}
|
|
|
- class="flex items-center justify-center rounded bg-blue-600 px-2 py-1.5 text-xs transition-colors hover:bg-blue-700"
|
|
|
- >
|
|
|
- <!--<img src={printerIcon} alt="Cozinha" class="mr-1 h-3.5 w-3.5" />-->
|
|
|
- Cozinha
|
|
|
- </button>
|
|
|
-
|
|
|
- <button
|
|
|
- on:click|stopPropagation={() => cancelOrder(table.id)}
|
|
|
- class="flex items-center justify-center rounded bg-red-600 px-2 py-1.5 text-xs transition-colors hover:bg-red-700"
|
|
|
- >
|
|
|
- <!--<img src={trashIcon} alt="Cancelar" class="mr-1 h-3.5 w-3.5" />-->
|
|
|
- Cancelar
|
|
|
- </button>
|
|
|
- </div>
|
|
|
+ <span class="font-medium">
|
|
|
+ R$ {order.totalAmount.toFixed(2)}
|
|
|
+ </span>
|
|
|
+ </div> -->
|
|
|
|
|
|
<button
|
|
|
- on:click={() => handleTableClick(table.id)}
|
|
|
+ on:click={() => handleTableClick(table.id, table.number)}
|
|
|
class="mt-2 flex w-full items-center justify-center rounded bg-indigo-600 px-3 py-2 text-sm transition-colors hover:bg-indigo-700"
|
|
|
>
|
|
|
- <!--<img src={creditCardIcon} alt="Comanda" class="mr-2 h-4 w-4" />-->
|
|
|
- Ver Comanda
|
|
|
+ Detalhes Mesa
|
|
|
</button>
|
|
|
</div>
|
|
|
{:else}
|
|
|
- <div class="flex flex-col items-center justify-center py-4">
|
|
|
+ <div class="flex flex-col items-center justify-center space-y-2 py-4">
|
|
|
<button
|
|
|
- on:click={() => handleTableClick(table.id)}
|
|
|
+ on:click={() => handleTableClick(table.id, table.number)}
|
|
|
class="flex items-center justify-center rounded bg-emerald-600 px-4 py-2 transition-colors hover:bg-emerald-700"
|
|
|
>
|
|
|
- <!--<img src={plusIcon} alt="Abrir Mesa" class="mr-2 h-5 w-5" />-->
|
|
|
Abrir Mesa
|
|
|
</button>
|
|
|
+ {#if flag === 'admin'}
|
|
|
+ <button
|
|
|
+ on:click={() => openDeleteConfirm(table.id, table.number)}
|
|
|
+ class="flex items-center justify-center rounded bg-red-600 px-4 py-2 text-sm transition-colors hover:bg-red-700"
|
|
|
+ >
|
|
|
+ Excluir Mesa
|
|
|
+ </button>
|
|
|
+ {/if}
|
|
|
</div>
|
|
|
{/if}
|
|
|
</div>
|
|
|
@@ -175,3 +322,26 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
+
|
|
|
+{#if showConfirmDelete}
|
|
|
+ <div class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
|
|
|
+ <div class="w-full max-w-sm rounded-lg bg-gray-800 p-6">
|
|
|
+ <h2 class="mb-4 text-lg font-bold">Confirmar Exclusão</h2>
|
|
|
+ <p class="mb-4">Deseja realmente excluir a Mesa {tableToDelete?.number}?</p>
|
|
|
+ <div class="flex justify-end gap-2">
|
|
|
+ <button
|
|
|
+ on:click={cancelDelete}
|
|
|
+ class="rounded bg-gray-600 px-4 py-2 text-white transition-colors hover:bg-gray-700"
|
|
|
+ >
|
|
|
+ Cancelar
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ on:click={deleteTable}
|
|
|
+ class="rounded bg-red-600 px-4 py-2 text-white transition-colors hover:bg-red-700"
|
|
|
+ >
|
|
|
+ Excluir
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+{/if}
|