Spaces:
Running
Running
Update cafe.html
Browse files
cafe.html
CHANGED
|
@@ -3,122 +3,561 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
| 6 |
-
<title>Cafe AI
|
|
|
|
| 7 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 8 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 9 |
-
<link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700;800&display=swap" rel="stylesheet">
|
|
|
|
| 10 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 11 |
-
<!-- Markdown parser -->
|
| 12 |
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
|
|
| 13 |
<script>
|
| 14 |
tailwind.config = {
|
| 15 |
theme: {
|
| 16 |
extend: {
|
| 17 |
fontFamily: {
|
| 18 |
sans: ['Vazirmatn', 'sans-serif'],
|
|
|
|
| 19 |
},
|
| 20 |
colors: {
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
},
|
| 28 |
-
|
| 29 |
-
DEFAULT: '#
|
| 30 |
-
light: '#
|
| 31 |
-
|
|
|
|
| 32 |
},
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
},
|
| 38 |
-
brass: '#c5a880',
|
| 39 |
-
emeraldMuted: '#10b981',
|
| 40 |
},
|
| 41 |
boxShadow: {
|
| 42 |
-
'
|
| 43 |
-
'
|
| 44 |
-
'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
},
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
},
|
| 49 |
}
|
| 50 |
}
|
| 51 |
}
|
| 52 |
</script>
|
|
|
|
| 53 |
<style>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
body {
|
| 55 |
-
background-color: #
|
| 56 |
-
color: #
|
| 57 |
-webkit-tap-highlight-color: transparent;
|
|
|
|
| 58 |
}
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
|
|
|
| 62 |
}
|
| 63 |
-
|
| 64 |
-
|
|
|
|
| 65 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
::-webkit-scrollbar-thumb {
|
| 67 |
-
background: #
|
| 68 |
border-radius: 4px;
|
| 69 |
}
|
|
|
|
|
|
|
| 70 |
.glass-card {
|
| 71 |
-
background: rgba(
|
| 72 |
-
backdrop-filter: blur(
|
| 73 |
-
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
}
|
| 76 |
.glass-card:hover {
|
| 77 |
-
border-color: rgba(
|
| 78 |
-
box-shadow:
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
.
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
</style>
|
| 89 |
</head>
|
| 90 |
-
<body class="min-h-screen flex flex-col font-sans selection:bg-
|
| 91 |
|
| 92 |
-
<!-- Ambient
|
| 93 |
-
<div class="
|
| 94 |
-
<div class="absolute -
|
| 95 |
-
<div class="absolute -
|
|
|
|
|
|
|
|
|
|
| 96 |
</div>
|
| 97 |
|
| 98 |
<!-- Header -->
|
| 99 |
-
<header class="border-b border-
|
| 100 |
-
<div class="max-w-7xl mx-auto flex items-center justify-between">
|
| 101 |
<div class="flex items-center gap-3">
|
| 102 |
-
<div class="
|
| 103 |
-
<
|
| 104 |
-
<
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
|
|
|
|
|
|
| 110 |
</div>
|
| 111 |
<div>
|
| 112 |
-
<h1 class="text-
|
| 113 |
-
|
|
|
|
|
|
|
| 114 |
</h1>
|
| 115 |
-
<p class="text-[10px] text-
|
| 116 |
</div>
|
| 117 |
</div>
|
| 118 |
-
|
| 119 |
-
<div class="flex items-center gap-
|
| 120 |
-
<span class="inline-flex items-center gap-
|
| 121 |
-
<span class="
|
| 122 |
نظارت زنده
|
| 123 |
</span>
|
| 124 |
</div>
|
|
@@ -127,189 +566,228 @@
|
|
| 127 |
|
| 128 |
<!-- Main grid -->
|
| 129 |
<main class="flex-1 max-w-7xl w-full mx-auto p-4 md:p-6 lg:p-8 grid grid-cols-1 lg:grid-cols-12 gap-6 z-10 relative">
|
| 130 |
-
|
| 131 |
<!-- Right column: orders + current menu + menu upload (7/12) -->
|
| 132 |
<section class="lg:col-span-7 flex flex-col gap-6">
|
| 133 |
-
|
| 134 |
-
<!-- Live orders monitor -->
|
| 135 |
-
<div class="glass-card rounded-3xl p-5 md:p-6 shadow-xl">
|
| 136 |
-
<div class="flex items-center justify-between mb-4 border-b border-tabriz-700/40 pb-3">
|
| 137 |
-
<div class="flex items-center gap-3">
|
| 138 |
-
<div class="w-3 h-3 rounded-full bg-gold animate-pulse shadow-gold-glow"></div>
|
| 139 |
-
<h2 class="text-sm md:text-base font-extrabold text-white flex items-center gap-2">
|
| 140 |
-
<svg class="w-5 h-5 text-turquoise" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
| 141 |
-
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" />
|
| 142 |
-
</svg>
|
| 143 |
-
سفارشات جدید آشپزخانه
|
| 144 |
-
</h2>
|
| 145 |
-
</div>
|
| 146 |
-
<button onclick="fetchActiveOrders()" class="text-[10px] text-turquoise/70 hover:text-gold flex items-center gap-1.5 transition-all duration-300 bg-tabriz-800/30 hover:bg-tabriz-700/40 px-3 py-1.5 rounded-xl">
|
| 147 |
-
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
| 148 |
-
<path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 1121.21 15H15" />
|
| 149 |
-
</svg>
|
| 150 |
-
بروزرسانی
|
| 151 |
-
</button>
|
| 152 |
-
</div>
|
| 153 |
-
<div id="ordersContainer" class="space-y-4 max-h-[350px] overflow-y-auto pr-1">
|
| 154 |
-
<div class="text-center py-12 text-xs text-turquoise/60">در حال بارگذاری لیست سفارشات جاری...</div>
|
| 155 |
-
</div>
|
| 156 |
-
</div>
|
| 157 |
|
| 158 |
-
<!--
|
| 159 |
-
<div class="glass-card rounded-3xl p-5 md:p-6
|
| 160 |
-
<div class="
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
| 170 |
-
<path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 1121.21 15H15"
|
| 171 |
</svg>
|
| 172 |
-
ب
|
| 173 |
-
</button>
|
| 174 |
-
<button onclick="addMenuItemRow()" class="px-4 py-1.5 bg-gradient-to-r from-turquoise to-cyan-600 hover:from-cyan-500 hover:to-turquoise rounded-xl text-[10px] font-extrabold text-white transition-all flex items-center gap-1 shadow-turquoise-glow">
|
| 175 |
-
<span>+ افزودن دستی</span>
|
| 176 |
</button>
|
| 177 |
</div>
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
</p>
|
| 182 |
-
<div class="overflow-x-auto max-h-[400px] border border-tabriz-700/40 rounded-2xl bg-tabriz-900/30">
|
| 183 |
-
<table class="min-w-full divide-y divide-tabriz-700/40 text-[11px]" id="currentMenuTable">
|
| 184 |
-
<thead class="bg-tabriz-800/50 sticky top-0">
|
| 185 |
-
<tr>
|
| 186 |
-
<th class="px-3 py-2.5 text-right font-medium text-turquoise/90">نام محصول</th>
|
| 187 |
-
<th class="px-3 py-2.5 text-right font-medium text-turquoise/90 hidden md:table-cell">توضیحات</th>
|
| 188 |
-
<th class="px-3 py-2.5 text-right font-medium text-turquoise/90 w-20">قیمت</th>
|
| 189 |
-
<th class="px-3 py-2.5 text-center font-medium text-turquoise/90 w-16">موجود</th>
|
| 190 |
-
<th class="px-3 py-2.5 text-center font-medium text-turquoise/90 w-20">عملیات</th>
|
| 191 |
-
</tr>
|
| 192 |
-
</thead>
|
| 193 |
-
<tbody id="menuTableBody" class="divide-y divide-tabriz-700/30">
|
| 194 |
-
<!-- filled by JS -->
|
| 195 |
-
</tbody>
|
| 196 |
-
</table>
|
| 197 |
</div>
|
| 198 |
</div>
|
| 199 |
|
| 200 |
-
<!-- Menu
|
| 201 |
-
<div class="glass-card rounded-3xl p-5 md:p-6 space-y-4 shadow-xl">
|
| 202 |
-
<
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
|
|
|
|
|
|
| 225 |
</div>
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
<
|
| 229 |
-
<
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
<h3 class="text-xs font-bold text-gold flex items-center gap-1.5">
|
| 233 |
-
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
| 234 |
-
<path stroke-linecap="round" stroke-linejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
| 235 |
-
</svg>
|
| 236 |
-
بازبینی اقلام منو پیش از انتشار زنده
|
| 237 |
-
</h3>
|
| 238 |
-
<div class="overflow-x-auto max-h-[250px] border border-tabriz-700/40 rounded-2xl bg-tabriz-900/30">
|
| 239 |
-
<table class="min-w-full divide-y divide-tabriz-700/40 text-[11px]">
|
| 240 |
-
<thead class="bg-tabriz-800/50">
|
| 241 |
<tr>
|
| 242 |
-
<th class="px-3 py-2.5 text-right
|
| 243 |
-
<th class="px-3 py-2.5 text-right
|
| 244 |
-
<th class="px-3 py-2.5 text-right
|
| 245 |
-
<th class="px-3 py-2.5 text-center
|
|
|
|
| 246 |
</tr>
|
| 247 |
</thead>
|
| 248 |
-
<tbody id="
|
| 249 |
-
<!--
|
| 250 |
</tbody>
|
| 251 |
</table>
|
| 252 |
</div>
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
</div>
|
| 257 |
</div>
|
| 258 |
</div>
|
| 259 |
</section>
|
| 260 |
|
| 261 |
<!-- Left column: Admin chat assistant (5/12) -->
|
| 262 |
-
<section class="lg:col-span-5 flex flex-col glass-card rounded-3xl h-[
|
| 263 |
-
<div class="
|
|
|
|
|
|
|
| 264 |
<div class="flex items-center gap-3">
|
| 265 |
-
<div class="w-
|
| 266 |
-
<svg class="w-
|
| 267 |
<path d="M12 2a10 10 0 0 1 10 10c0 5.523-4.477 10-10 10S2 17.523 2 12A10 10 0 0 1 12 2z"/>
|
| 268 |
-
<path d="M12 8v4l3 3"/>
|
| 269 |
</svg>
|
| 270 |
</div>
|
| 271 |
<div>
|
| 272 |
-
<h3 class="
|
| 273 |
-
<p class="text-[
|
|
|
|
|
|
|
|
|
|
| 274 |
</div>
|
| 275 |
</div>
|
| 276 |
</div>
|
| 277 |
|
| 278 |
-
<div id="chatMessages" class="flex-1 overflow-y-auto p-4 space-y-4">
|
| 279 |
<div class="flex gap-2.5 max-w-[85%]">
|
| 280 |
-
<div class="w-8 h-8 rounded-xl bg-
|
| 281 |
-
<span class="text-
|
| 282 |
</div>
|
| 283 |
-
<div class="bg-
|
| 284 |
-
سلام همکار گرامی! من نیلا هستم، دستیار مدیریت منوی Cafe AI. شما میتوانید از همینجا اقلام منو را با گفتگوی ساده کنترل کنید؛ برای مثال بنویسید:
|
| 285 |
</div>
|
| 286 |
</div>
|
| 287 |
</div>
|
| 288 |
|
| 289 |
-
<div id="chatLoader" class="hidden px-4 py-
|
| 290 |
-
<div class="flex items-center gap-2">
|
| 291 |
-
<div class="flex
|
| 292 |
-
<span
|
| 293 |
-
<span class="w-1.5 h-1.5 bg-gold rounded-full animate-bounce" style="animation-delay: 0.2s"></span>
|
| 294 |
-
<span class="w-1.5 h-1.5 bg-gold rounded-full animate-bounce" style="animation-delay: 0.3s"></span>
|
| 295 |
</div>
|
| 296 |
-
<span>نیلا در حال پردازش
|
| 297 |
</div>
|
| 298 |
-
<button onclick="stopAdminChat()" class="px-
|
|
|
|
|
|
|
| 299 |
</div>
|
| 300 |
|
| 301 |
-
<form id="chatForm" onsubmit="sendAdminMessage(event)" class="p-3 border-t border-
|
| 302 |
-
<input type="text" id="chatInput" placeholder="تغییر وضعیت یا ویرایش منو را
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
|
|
|
| 306 |
</svg>
|
| 307 |
</button>
|
| 308 |
</form>
|
| 309 |
</section>
|
| 310 |
</main>
|
| 311 |
|
| 312 |
-
<footer class="border-t border-
|
| 313 |
طراحی و توسعه توسط الگوریتم داده نسترن | با الهام از فرهنگ تبریز © ۲۰۲۶
|
| 314 |
</footer>
|
| 315 |
|
|
@@ -318,7 +796,7 @@
|
|
| 318 |
marked.setOptions({ sanitize: true, breaks: true });
|
| 319 |
|
| 320 |
let chatHistory = [
|
| 321 |
-
{ role: 'model', content: 'سلام همکار گرامی! من نیلا هستم، دستیار مدیریت منوی Cafe AI. شما میتوانید از همینجا اقلام منو را با گفتگوی ساده کنترل کنید؛ برای مثال بنویسید:
|
| 322 |
];
|
| 323 |
let abortController = null;
|
| 324 |
let isGenerating = false;
|
|
@@ -329,7 +807,7 @@
|
|
| 329 |
setInterval(fetchActiveOrders, 10000);
|
| 330 |
});
|
| 331 |
|
| 332 |
-
// ==================== Order functions
|
| 333 |
async function fetchActiveOrders() {
|
| 334 |
const container = document.getElementById('ordersContainer');
|
| 335 |
try {
|
|
@@ -338,11 +816,12 @@
|
|
| 338 |
if (response.ok) {
|
| 339 |
if (orders.length === 0) {
|
| 340 |
container.innerHTML = `
|
| 341 |
-
<div class="text-center py-16 text-
|
| 342 |
-
<svg class="w-
|
| 343 |
-
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
| 344 |
</svg>
|
| 345 |
-
در حال حاضر سفارش معلق
|
|
|
|
| 346 |
</div>
|
| 347 |
`;
|
| 348 |
return;
|
|
@@ -352,27 +831,42 @@
|
|
| 352 |
let itemsHtml = '';
|
| 353 |
order.items.forEach(item => {
|
| 354 |
itemsHtml += `
|
| 355 |
-
<div class="flex items-center justify-between text-[11px] bg-
|
| 356 |
-
<span class="text-
|
| 357 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 358 |
</div>
|
| 359 |
`;
|
| 360 |
});
|
| 361 |
const timeString = new Date(order.timestamp * 1000).toLocaleTimeString('fa-IR', { hour: '2-digit', minute: '2-digit' });
|
| 362 |
const cardHtml = `
|
| 363 |
-
<div class="
|
| 364 |
<div class="flex-1 space-y-2 w-full">
|
| 365 |
-
<div class="flex items-center justify-between md:justify-start gap-3 w-full">
|
| 366 |
-
<span class="text-xs font-bold
|
| 367 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 368 |
</div>
|
| 369 |
<div class="grid grid-cols-1 sm:grid-cols-2 gap-1.5 mt-2">
|
| 370 |
${itemsHtml}
|
| 371 |
</div>
|
| 372 |
</div>
|
| 373 |
-
<button onclick="completeOrder(${order.id})" class="w-full md:w-auto
|
| 374 |
-
<svg class="w-
|
| 375 |
-
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"
|
| 376 |
</svg>
|
| 377 |
تحویل نهایی شد
|
| 378 |
</button>
|
|
@@ -381,10 +875,10 @@
|
|
| 381 |
container.insertAdjacentHTML('beforeend', cardHtml);
|
| 382 |
});
|
| 383 |
} else {
|
| 384 |
-
container.innerHTML = `<div class="text-center py-6 text-
|
| 385 |
}
|
| 386 |
} catch (err) {
|
| 387 |
-
container.innerHTML = `<div class="text-center py-6 text-
|
| 388 |
}
|
| 389 |
}
|
| 390 |
|
|
@@ -406,20 +900,20 @@
|
|
| 406 |
}
|
| 407 |
}
|
| 408 |
|
| 409 |
-
// ==================== Menu CRUD
|
| 410 |
async function fetchCurrentMenu() {
|
| 411 |
const tbody = document.getElementById('menuTableBody');
|
| 412 |
-
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-8 text-
|
| 413 |
try {
|
| 414 |
const res = await fetch('/api/admin/menu');
|
| 415 |
const menu = await res.json();
|
| 416 |
if (res.ok && Array.isArray(menu)) {
|
| 417 |
renderMenuTable(menu);
|
| 418 |
} else {
|
| 419 |
-
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-8 text-
|
| 420 |
}
|
| 421 |
} catch (e) {
|
| 422 |
-
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-8 text-
|
| 423 |
}
|
| 424 |
}
|
| 425 |
|
|
@@ -427,46 +921,48 @@
|
|
| 427 |
const tbody = document.getElementById('menuTableBody');
|
| 428 |
tbody.innerHTML = '';
|
| 429 |
if (menu.length === 0) {
|
| 430 |
-
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-8 text-
|
| 431 |
return;
|
| 432 |
}
|
| 433 |
menu.forEach(item => {
|
| 434 |
const row = document.createElement('tr');
|
| 435 |
-
row.className = '
|
| 436 |
row.dataset.id = item.id;
|
| 437 |
row.innerHTML = `
|
| 438 |
-
<td class="px-2 py-
|
| 439 |
-
<span class="view-mode font-bold text-
|
| 440 |
-
<input type="text" class="edit-mode hidden w-full bg-
|
| 441 |
</td>
|
| 442 |
-
<td class="px-2 py-
|
| 443 |
-
<span class="view-mode text-
|
| 444 |
-
<input type="text" class="edit-mode hidden w-full bg-
|
| 445 |
</td>
|
| 446 |
-
<td class="px-2 py-
|
| 447 |
-
<span class="view-mode text-
|
| 448 |
-
<input type="text" class="edit-mode hidden w-full bg-
|
| 449 |
</td>
|
| 450 |
-
<td class="px-2 py-
|
| 451 |
-
<span class="view-mode text-
|
| 452 |
-
<
|
|
|
|
|
|
|
| 453 |
</td>
|
| 454 |
-
<td class="px-2 py-
|
| 455 |
<div class="view-mode flex gap-1 justify-center">
|
| 456 |
-
<button onclick="editMenuItem(this)" class="p-1 text-
|
| 457 |
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
| 458 |
-
<path stroke-linecap="round" stroke-linejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
|
| 459 |
</svg>
|
| 460 |
</button>
|
| 461 |
-
<button onclick="deleteMenuItem(${item.id})" class="p-1 text-
|
| 462 |
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
| 463 |
-
<path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
| 464 |
</svg>
|
| 465 |
</button>
|
| 466 |
</div>
|
| 467 |
<div class="edit-mode hidden flex gap-1 justify-center">
|
| 468 |
-
<button onclick="saveMenuItem(this, ${item.id})" class="px-2 py-1 bg-emerald-
|
| 469 |
-
<button onclick="cancelEdit(this)" class="px-2 py-1 bg-
|
| 470 |
</div>
|
| 471 |
</td>
|
| 472 |
`;
|
|
@@ -491,9 +987,6 @@
|
|
| 491 |
}
|
| 492 |
|
| 493 |
function cancelEdit(btn) {
|
| 494 |
-
const row = btn.closest('tr');
|
| 495 |
-
// Reset fields to original (re-fetch from data attributes? we can just store original values in dataset)
|
| 496 |
-
// Simple: reload menu
|
| 497 |
fetchCurrentMenu();
|
| 498 |
}
|
| 499 |
|
|
@@ -546,26 +1039,25 @@
|
|
| 546 |
}
|
| 547 |
}
|
| 548 |
|
| 549 |
-
|
| 550 |
-
// Create a new empty row with inputs and a save button
|
| 551 |
const tbody = document.getElementById('menuTableBody');
|
| 552 |
const row = document.createElement('tr');
|
| 553 |
-
row.className = 'bg-
|
| 554 |
row.innerHTML = `
|
| 555 |
-
<td class="px-2 py-
|
| 556 |
-
<input type="text" class="w-full bg-
|
| 557 |
</td>
|
| 558 |
-
<td class="px-2 py-
|
| 559 |
-
<input type="text" class="w-full bg-
|
| 560 |
</td>
|
| 561 |
-
<td class="px-2 py-
|
| 562 |
-
<input type="text" class="w-full bg-
|
| 563 |
</td>
|
| 564 |
-
<td class="px-2 py-
|
| 565 |
<input type="checkbox" data-field="available" checked>
|
| 566 |
</td>
|
| 567 |
-
<td class="px-2 py-
|
| 568 |
-
<button onclick="saveNewItem(this)" class="px-3 py-1
|
| 569 |
</td>
|
| 570 |
`;
|
| 571 |
tbody.appendChild(row);
|
|
@@ -598,20 +1090,16 @@
|
|
| 598 |
}
|
| 599 |
}
|
| 600 |
|
| 601 |
-
// ==================== Menu extraction
|
| 602 |
function handleDragOver(e) {
|
| 603 |
e.preventDefault();
|
| 604 |
const dropZone = document.getElementById('dropZone');
|
| 605 |
-
|
| 606 |
-
dropZone.classList.add('border-gold', 'bg-gold/5');
|
| 607 |
-
icon.classList.add('text-gold');
|
| 608 |
}
|
| 609 |
function handleDragLeave(e) {
|
| 610 |
e.preventDefault();
|
| 611 |
const dropZone = document.getElementById('dropZone');
|
| 612 |
-
|
| 613 |
-
dropZone.classList.remove('border-gold', 'bg-gold/5');
|
| 614 |
-
icon.classList.remove('text-gold');
|
| 615 |
}
|
| 616 |
function handleDrop(e) {
|
| 617 |
e.preventDefault();
|
|
@@ -656,20 +1144,20 @@
|
|
| 656 |
tbody.innerHTML = '';
|
| 657 |
menuItems.forEach((item, index) => {
|
| 658 |
const row = `
|
| 659 |
-
<tr class="
|
| 660 |
<td class="px-2 py-2">
|
| 661 |
-
<input type="text" class="menu-name w-full bg-
|
| 662 |
</td>
|
| 663 |
<td class="px-2 py-2">
|
| 664 |
-
<input type="text" class="menu-desc w-full bg-
|
| 665 |
</td>
|
| 666 |
<td class="px-2 py-2">
|
| 667 |
-
<input type="text" class="menu-price w-full bg-
|
| 668 |
</td>
|
| 669 |
<td class="px-2 py-2 text-center">
|
| 670 |
-
<button onclick="removeMenuRow(this)" class="p-1 text-
|
| 671 |
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
| 672 |
-
<path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
| 673 |
</svg>
|
| 674 |
</button>
|
| 675 |
</td>
|
|
@@ -681,20 +1169,20 @@
|
|
| 681 |
function addEmptyMenuRow() {
|
| 682 |
const tbody = document.getElementById('menuEditorTableBody');
|
| 683 |
const row = `
|
| 684 |
-
<tr class="
|
| 685 |
<td class="px-2 py-2">
|
| 686 |
-
<input type="text" class="menu-name w-full bg-
|
| 687 |
</td>
|
| 688 |
<td class="px-2 py-2">
|
| 689 |
-
<input type="text" class="menu-desc w-full bg-
|
| 690 |
</td>
|
| 691 |
<td class="px-2 py-2">
|
| 692 |
-
<input type="text" class="menu-price w-full bg-
|
| 693 |
</td>
|
| 694 |
<td class="px-2 py-2 text-center">
|
| 695 |
-
<button onclick="removeMenuRow(this)" class="p-1 text-
|
| 696 |
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
| 697 |
-
<path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
| 698 |
</svg>
|
| 699 |
</button>
|
| 700 |
</td>
|
|
@@ -729,10 +1217,10 @@
|
|
| 729 |
});
|
| 730 |
const data = await response.json();
|
| 731 |
if (response.ok && data.success) {
|
| 732 |
-
alert("منوی جدید با موفقیت ثبت و روی وبسایت همگامسازی شد!");
|
| 733 |
document.getElementById('menuEditorContainer').classList.add('hidden');
|
| 734 |
-
fetchCurrentMenu();
|
| 735 |
-
appendChatMessage('model', 'منوی زنده همگامسازی شد. سیستم سفارشگیری مشتریان اکنون بر مبنای آخرین لیست ویرایششده کالیبره شده است.');
|
| 736 |
} else {
|
| 737 |
alert(data.error || 'بروز خطا در همگامسازی ساختار دادههای جدید.');
|
| 738 |
}
|
|
@@ -741,7 +1229,7 @@
|
|
| 741 |
}
|
| 742 |
}
|
| 743 |
|
| 744 |
-
// ==================== Admin Chat
|
| 745 |
async function sendAdminMessage(e) {
|
| 746 |
e.preventDefault();
|
| 747 |
if (isGenerating) return;
|
|
@@ -769,11 +1257,10 @@
|
|
| 769 |
isGenerating = false;
|
| 770 |
|
| 771 |
if (response.ok && data.success) {
|
| 772 |
-
const botBubbleId = appendChatMessage('model', '');
|
| 773 |
const botBubble = document.getElementById(botBubbleId);
|
| 774 |
const textResponse = data.response;
|
| 775 |
let i = 0;
|
| 776 |
-
// Typewriter effect with plain text
|
| 777 |
function typeText() {
|
| 778 |
if (i < textResponse.length && !abortController.signal.aborted) {
|
| 779 |
botBubble.textContent += textResponse.charAt(i);
|
|
@@ -781,11 +1268,9 @@
|
|
| 781 |
document.getElementById('chatMessages').scrollTop = document.getElementById('chatMessages').scrollHeight;
|
| 782 |
setTimeout(typeText, 6);
|
| 783 |
} else {
|
| 784 |
-
|
| 785 |
-
botBubble.textContent = ''; // clear
|
| 786 |
botBubble.innerHTML = marked.parse(textResponse);
|
| 787 |
chatHistory.push({ role: 'model', content: textResponse });
|
| 788 |
-
// After Nila might have changed menu, refresh the menu list
|
| 789 |
fetchCurrentMenu();
|
| 790 |
}
|
| 791 |
}
|
|
@@ -797,7 +1282,7 @@
|
|
| 797 |
toggleChatLoading(false);
|
| 798 |
isGenerating = false;
|
| 799 |
if (err.name !== 'AbortError') {
|
| 800 |
-
appendChatMessage('model', 'عدم دریافت موفق پاسخ در بستر شبکه: ' + err.message);
|
| 801 |
}
|
| 802 |
}
|
| 803 |
}
|
|
@@ -827,26 +1312,29 @@
|
|
| 827 |
|
| 828 |
function appendChatMessage(role, content) {
|
| 829 |
const container = document.getElementById('chatMessages');
|
| 830 |
-
const bubbleId = 'bubble-admin-' + Date.now();
|
| 831 |
let html = '';
|
| 832 |
|
| 833 |
if (role === 'user') {
|
| 834 |
html = `
|
| 835 |
<div class="flex justify-end gap-2.5 max-w-[85%] mr-auto">
|
| 836 |
-
<div class="bg-
|
| 837 |
${escapeHtml(content)}
|
| 838 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 839 |
</div>
|
| 840 |
`;
|
| 841 |
} else {
|
| 842 |
-
// For model, we use innerHTML with chat-markdown class
|
| 843 |
-
// Note: content will be set later (initially empty for typewriter)
|
| 844 |
html = `
|
| 845 |
<div class="flex gap-2.5 max-w-[85%]">
|
| 846 |
-
<div class="w-8 h-8 rounded-xl bg-
|
| 847 |
-
<span class="text-
|
| 848 |
</div>
|
| 849 |
-
<div id="${bubbleId}" class="bg-
|
| 850 |
${content ? marked.parse(content) : ''}
|
| 851 |
</div>
|
| 852 |
</div>
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
| 6 |
+
<title>Cafe AI — پنل مدیریت</title>
|
| 7 |
+
|
| 8 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 9 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Lalezar&family=Vazirmatn:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
|
| 11 |
+
|
| 12 |
<script src="https://cdn.tailwindcss.com"></script>
|
|
|
|
| 13 |
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
| 14 |
+
|
| 15 |
<script>
|
| 16 |
tailwind.config = {
|
| 17 |
theme: {
|
| 18 |
extend: {
|
| 19 |
fontFamily: {
|
| 20 |
sans: ['Vazirmatn', 'sans-serif'],
|
| 21 |
+
display: ['Lalezar', 'serif'],
|
| 22 |
},
|
| 23 |
colors: {
|
| 24 |
+
bazaar: {
|
| 25 |
+
950: '#1a0d07',
|
| 26 |
+
900: '#2d1810',
|
| 27 |
+
800: '#3d2418',
|
| 28 |
+
700: '#5c3a2a',
|
| 29 |
+
600: '#7a4e3a',
|
| 30 |
+
500: '#9c6b4f',
|
| 31 |
+
400: '#b8856a',
|
| 32 |
+
300: '#d4a88c',
|
| 33 |
+
},
|
| 34 |
+
azure: {
|
| 35 |
+
950: '#051a2e',
|
| 36 |
+
900: '#0a2540',
|
| 37 |
+
800: '#123a5e',
|
| 38 |
+
700: '#1e4976',
|
| 39 |
+
600: '#2d6ca8',
|
| 40 |
+
500: '#3d8bc4',
|
| 41 |
+
400: '#6ba7d4',
|
| 42 |
+
300: '#a3c8e4',
|
| 43 |
+
},
|
| 44 |
+
turk: {
|
| 45 |
+
DEFAULT: '#0d9488',
|
| 46 |
+
light: '#2dd4bf',
|
| 47 |
+
glow: '#5eead4',
|
| 48 |
+
deep: '#134e4a',
|
| 49 |
+
},
|
| 50 |
+
rug: {
|
| 51 |
+
DEFAULT: '#8b1e3f',
|
| 52 |
+
light: '#c23d5f',
|
| 53 |
+
dark: '#5a1028',
|
| 54 |
+
glow: '#e85d7e',
|
| 55 |
},
|
| 56 |
+
ochre: {
|
| 57 |
+
DEFAULT: '#c9a961',
|
| 58 |
+
light: '#e4c97a',
|
| 59 |
+
dark: '#9c7d3a',
|
| 60 |
+
pale: '#f0dca3',
|
| 61 |
},
|
| 62 |
+
ivory: '#faf3e7',
|
| 63 |
+
cream: '#f5e6d3',
|
| 64 |
+
saffron: '#d97706',
|
| 65 |
+
emerald: {
|
| 66 |
+
DEFAULT: '#10b981',
|
| 67 |
+
light: '#34d399',
|
| 68 |
+
dark: '#065f46',
|
| 69 |
+
},
|
| 70 |
+
rose: {
|
| 71 |
+
DEFAULT: '#e11d48',
|
| 72 |
+
light: '#fb7185',
|
| 73 |
},
|
|
|
|
|
|
|
| 74 |
},
|
| 75 |
boxShadow: {
|
| 76 |
+
'carpet': '0 10px 40px -10px rgba(139, 30, 63, 0.35), 0 0 0 1px rgba(201, 169, 97, 0.15)',
|
| 77 |
+
'mosque': '0 10px 40px -10px rgba(13, 148, 136, 0.4), 0 0 0 1px rgba(45, 212, 191, 0.2)',
|
| 78 |
+
'bazaar-glow': '0 0 60px rgba(201, 169, 97, 0.25), inset 0 1px 0 rgba(244, 228, 163, 0.1)',
|
| 79 |
+
'inner-soft': 'inset 0 2px 8px rgba(0,0,0,0.35)',
|
| 80 |
+
'inner-gold': 'inset 0 0 20px rgba(201, 169, 97, 0.15)',
|
| 81 |
+
},
|
| 82 |
+
animation: {
|
| 83 |
+
'float': 'float 6s ease-in-out infinite',
|
| 84 |
+
'shimmer': 'shimmer 3s ease-in-out infinite',
|
| 85 |
+
'pulse-glow': 'pulseGlow 2.5s ease-in-out infinite',
|
| 86 |
+
'ping-slow': 'ping 2.5s cubic-bezier(0, 0, 0.2, 1) infinite',
|
| 87 |
},
|
| 88 |
+
keyframes: {
|
| 89 |
+
float: {
|
| 90 |
+
'0%, 100%': { transform: 'translateY(0px)' },
|
| 91 |
+
'50%': { transform: 'translateY(-12px)' },
|
| 92 |
+
},
|
| 93 |
+
shimmer: {
|
| 94 |
+
'0%, 100%': { opacity: '0.7' },
|
| 95 |
+
'50%': { opacity: '1' },
|
| 96 |
+
},
|
| 97 |
+
pulseGlow: {
|
| 98 |
+
'0%, 100%': { boxShadow: '0 0 20px rgba(201, 169, 97, 0.3)' },
|
| 99 |
+
'50%': { boxShadow: '0 0 40px rgba(201, 169, 97, 0.6)' },
|
| 100 |
+
},
|
| 101 |
},
|
| 102 |
}
|
| 103 |
}
|
| 104 |
}
|
| 105 |
</script>
|
| 106 |
+
|
| 107 |
<style>
|
| 108 |
+
:root {
|
| 109 |
+
--pattern-url: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='80' viewBox='0 0 80 80'%3E%3Cg fill='none' stroke='%23c9a961' stroke-width='0.6' opacity='0.08'%3E%3Cpath d='M40 10 L50 20 L60 10 L70 20 L60 30 L70 40 L60 50 L70 60 L60 70 L50 60 L40 70 L30 60 L20 70 L10 60 L20 50 L10 40 L20 30 L10 20 L20 10 L30 20 Z'/%3E%3Ccircle cx='40' cy='40' r='12'/%3E%3Ccircle cx='40' cy='40' r='6'/%3E%3Cpath d='M40 28 L40 52 M28 40 L52 40'/%3E%3C/g%3E%3C/svg%3E");
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
html { scroll-behavior: smooth; }
|
| 113 |
+
|
| 114 |
body {
|
| 115 |
+
background-color: #1a0d07;
|
| 116 |
+
color: #f5e6d3;
|
| 117 |
-webkit-tap-highlight-color: transparent;
|
| 118 |
+
font-feature-settings: "ss01", "cv01";
|
| 119 |
}
|
| 120 |
+
|
| 121 |
+
.bg-pattern {
|
| 122 |
+
background-image: var(--pattern-url);
|
| 123 |
+
background-size: 80px 80px;
|
| 124 |
}
|
| 125 |
+
|
| 126 |
+
.bg-pattern-fine {
|
| 127 |
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='0 0 40 40'%3E%3Cg fill='none' stroke='%23c9a961' stroke-width='0.4' opacity='0.1'%3E%3Cpath d='M20 0 L20 40 M0 20 L40 20'/%3E%3Ccircle cx='20' cy='20' r='3'/%3E%3Ccircle cx='0' cy='0' r='2'/%3E%3Ccircle cx='40' cy='0' r='2'/%3E%3Ccircle cx='0' cy='40' r='2'/%3E%3Ccircle cx='40' cy='40' r='2'/%3E%3C/g%3E%3C/svg%3E");
|
| 128 |
}
|
| 129 |
+
|
| 130 |
+
.bg-arch {
|
| 131 |
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200' viewBox='0 0 200 200'%3E%3Cg fill='none' stroke='%230d9488' stroke-width='0.5' opacity='0.06'%3E%3Cpath d='M100 20 Q 40 40, 40 100 Q 40 180, 100 180 Q 160 180, 160 100 Q 160 40, 100 20 Z'/%3E%3Cpath d='M100 50 Q 65 65, 65 100 Q 65 155, 100 155 Q 135 155, 135 100 Q 135 65, 100 50 Z'/%3E%3Cpath d='M100 80 Q 85 90, 85 100 Q 85 130, 100 130 Q 115 130, 115 100 Q 115 90, 100 80 Z'/%3E%3C/g%3E%3C/svg%3E");
|
| 132 |
+
background-size: 200px 200px;
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
/* Scrollbar */
|
| 136 |
+
::-webkit-scrollbar { width: 6px; height: 6px; }
|
| 137 |
+
::-webkit-scrollbar-track { background: #1a0d07; }
|
| 138 |
::-webkit-scrollbar-thumb {
|
| 139 |
+
background: linear-gradient(180deg, #c9a961, #8b1e3f);
|
| 140 |
border-radius: 4px;
|
| 141 |
}
|
| 142 |
+
::-webkit-scrollbar-thumb:hover { background: linear-gradient(180deg, #e4c97a, #c23d5f); }
|
| 143 |
+
|
| 144 |
.glass-card {
|
| 145 |
+
background: linear-gradient(135deg, rgba(45, 24, 16, 0.82) 0%, rgba(26, 13, 7, 0.78) 100%);
|
| 146 |
+
backdrop-filter: blur(20px);
|
| 147 |
+
-webkit-backdrop-filter: blur(20px);
|
| 148 |
+
border: 1px solid rgba(201, 169, 97, 0.18);
|
| 149 |
+
box-shadow:
|
| 150 |
+
0 20px 50px -20px rgba(0, 0, 0, 0.6),
|
| 151 |
+
inset 0 1px 0 rgba(244, 228, 163, 0.08);
|
| 152 |
+
transition: all 0.4s ease;
|
| 153 |
}
|
| 154 |
.glass-card:hover {
|
| 155 |
+
border-color: rgba(201, 169, 97, 0.3);
|
| 156 |
+
box-shadow:
|
| 157 |
+
0 25px 60px -20px rgba(0, 0, 0, 0.7),
|
| 158 |
+
0 0 40px rgba(201, 169, 97, 0.1),
|
| 159 |
+
inset 0 1px 0 rgba(244, 228, 163, 0.12);
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
.glass-card-azure {
|
| 163 |
+
background: linear-gradient(135deg, rgba(10, 37, 64, 0.78) 0%, rgba(5, 26, 46, 0.82) 100%);
|
| 164 |
+
backdrop-filter: blur(20px);
|
| 165 |
+
-webkit-backdrop-filter: blur(20px);
|
| 166 |
+
border: 1px solid rgba(45, 212, 191, 0.2);
|
| 167 |
+
box-shadow:
|
| 168 |
+
0 20px 50px -20px rgba(13, 148, 136, 0.2),
|
| 169 |
+
inset 0 1px 0 rgba(94, 234, 212, 0.1);
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
/* Ornament divider */
|
| 173 |
+
.ornament-divider {
|
| 174 |
+
position: relative;
|
| 175 |
+
text-align: center;
|
| 176 |
+
}
|
| 177 |
+
.ornament-divider::before,
|
| 178 |
+
.ornament-divider::after {
|
| 179 |
+
content: '';
|
| 180 |
+
position: absolute;
|
| 181 |
+
top: 50%;
|
| 182 |
+
width: 30%;
|
| 183 |
+
height: 1px;
|
| 184 |
+
background: linear-gradient(90deg, transparent, rgba(201, 169, 97, 0.5), transparent);
|
| 185 |
+
}
|
| 186 |
+
.ornament-divider::before { left: 0; }
|
| 187 |
+
.ornament-divider::after { right: 0; }
|
| 188 |
+
|
| 189 |
+
/* Arch shape */
|
| 190 |
+
.arch-shape {
|
| 191 |
+
border-radius: 50% 50% 1rem 1rem / 35% 35% 1rem 1rem;
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
/* Chat Markdown - پشتیبانی کامل */
|
| 195 |
+
.chat-markdown { line-height: 1.85; }
|
| 196 |
+
.chat-markdown > * + * { margin-top: 0.6em; }
|
| 197 |
+
|
| 198 |
+
.chat-markdown h1, .chat-markdown h2, .chat-markdown h3,
|
| 199 |
+
.chat-markdown h4, .chat-markdown h5, .chat-markdown h6 {
|
| 200 |
+
font-family: 'Lalezar', serif;
|
| 201 |
+
color: #e4c97a;
|
| 202 |
+
margin-top: 1em;
|
| 203 |
+
margin-bottom: 0.4em;
|
| 204 |
+
line-height: 1.3;
|
| 205 |
+
}
|
| 206 |
+
.chat-markdown h1 { font-size: 1.4em; }
|
| 207 |
+
.chat-markdown h2 { font-size: 1.25em; }
|
| 208 |
+
.chat-markdown h3 { font-size: 1.15em; color: #5eead4; }
|
| 209 |
+
.chat-markdown h4, .chat-markdown h5, .chat-markdown h6 { font-size: 1.05em; }
|
| 210 |
+
|
| 211 |
+
.chat-markdown p { margin-bottom: 0.5em; }
|
| 212 |
+
|
| 213 |
+
.chat-markdown ul, .chat-markdown ol {
|
| 214 |
+
padding-right: 1.4em;
|
| 215 |
+
margin: 0.4em 0;
|
| 216 |
+
}
|
| 217 |
+
.chat-markdown ul { list-style-type: '❋ '; }
|
| 218 |
+
.chat-markdown ol { list-style-type: persian; }
|
| 219 |
+
.chat-markdown li { margin-bottom: 0.3em; padding-right: 0.2em; }
|
| 220 |
+
.chat-markdown li::marker { color: #c9a961; }
|
| 221 |
+
|
| 222 |
+
.chat-markdown strong {
|
| 223 |
+
color: #f0dca3;
|
| 224 |
+
font-weight: 700;
|
| 225 |
+
text-shadow: 0 0 8px rgba(201, 169, 97, 0.3);
|
| 226 |
+
}
|
| 227 |
+
.chat-markdown em {
|
| 228 |
+
color: #5eead4;
|
| 229 |
+
font-style: italic;
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
.chat-markdown a {
|
| 233 |
+
color: #2dd4bf;
|
| 234 |
+
text-decoration: underline;
|
| 235 |
+
text-underline-offset: 3px;
|
| 236 |
+
transition: color 0.2s;
|
| 237 |
+
}
|
| 238 |
+
.chat-markdown a:hover { color: #5eead4; }
|
| 239 |
+
|
| 240 |
+
.chat-markdown code {
|
| 241 |
+
background: rgba(13, 148, 136, 0.18);
|
| 242 |
+
color: #5eead4;
|
| 243 |
+
padding: 0.15em 0.4em;
|
| 244 |
+
border-radius: 5px;
|
| 245 |
+
font-size: 0.88em;
|
| 246 |
+
border: 1px solid rgba(45, 212, 191, 0.2);
|
| 247 |
+
font-family: 'Courier New', monospace;
|
| 248 |
+
direction: ltr;
|
| 249 |
+
display: inline-block;
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
.chat-markdown pre {
|
| 253 |
+
background: rgba(5, 26, 46, 0.7);
|
| 254 |
+
border: 1px solid rgba(45, 212, 191, 0.25);
|
| 255 |
+
border-right: 3px solid #0d9488;
|
| 256 |
+
padding: 0.9em 1em;
|
| 257 |
+
border-radius: 8px;
|
| 258 |
+
overflow-x: auto;
|
| 259 |
+
margin: 0.6em 0;
|
| 260 |
+
direction: ltr;
|
| 261 |
+
text-align: left;
|
| 262 |
+
}
|
| 263 |
+
.chat-markdown pre code {
|
| 264 |
+
background: transparent;
|
| 265 |
+
border: none;
|
| 266 |
+
padding: 0;
|
| 267 |
+
color: #a3c8e4;
|
| 268 |
+
font-size: 0.85em;
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
.chat-markdown blockquote {
|
| 272 |
+
border-right: 3px solid #c9a961;
|
| 273 |
+
background: linear-gradient(90deg, rgba(201, 169, 97, 0.08), transparent);
|
| 274 |
+
padding: 0.5em 1em;
|
| 275 |
+
margin: 0.6em 0;
|
| 276 |
+
color: #f0dca3;
|
| 277 |
+
font-style: italic;
|
| 278 |
+
border-radius: 0 8px 8px 0;
|
| 279 |
+
}
|
| 280 |
+
.chat-markdown blockquote p { margin: 0; }
|
| 281 |
+
|
| 282 |
+
.chat-markdown hr {
|
| 283 |
+
border: none;
|
| 284 |
+
height: 1px;
|
| 285 |
+
background: linear-gradient(90deg, transparent, rgba(201, 169, 97, 0.5), transparent);
|
| 286 |
+
margin: 1em 0;
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
.chat-markdown table {
|
| 290 |
+
width: 100%;
|
| 291 |
+
border-collapse: collapse;
|
| 292 |
+
margin: 0.6em 0;
|
| 293 |
+
font-size: 0.92em;
|
| 294 |
+
background: rgba(45, 24, 16, 0.5);
|
| 295 |
+
border-radius: 8px;
|
| 296 |
+
overflow: hidden;
|
| 297 |
+
border: 1px solid rgba(201, 169, 97, 0.25);
|
| 298 |
+
}
|
| 299 |
+
.chat-markdown thead {
|
| 300 |
+
background: linear-gradient(90deg, rgba(139, 30, 63, 0.4), rgba(201, 169, 97, 0.2));
|
| 301 |
+
}
|
| 302 |
+
.chat-markdown th {
|
| 303 |
+
padding: 0.55em 0.8em;
|
| 304 |
+
text-align: right;
|
| 305 |
+
color: #e4c97a;
|
| 306 |
+
font-weight: 700;
|
| 307 |
+
border-bottom: 1px solid rgba(201, 169, 97, 0.3);
|
| 308 |
+
font-family: 'Lalezar', serif;
|
| 309 |
+
}
|
| 310 |
+
.chat-markdown td {
|
| 311 |
+
padding: 0.5em 0.8em;
|
| 312 |
+
border-bottom: 1px solid rgba(201, 169, 97, 0.1);
|
| 313 |
+
}
|
| 314 |
+
.chat-markdown tr:last-child td { border-bottom: none; }
|
| 315 |
+
.chat-markdown tr:hover td { background: rgba(201, 169, 97, 0.05); }
|
| 316 |
+
|
| 317 |
+
.chat-markdown img {
|
| 318 |
+
max-width: 100%;
|
| 319 |
+
border-radius: 10px;
|
| 320 |
+
border: 2px solid rgba(201, 169, 97, 0.3);
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
/* Typing dots */
|
| 324 |
+
.typing-dots span {
|
| 325 |
+
display: inline-block;
|
| 326 |
+
width: 6px;
|
| 327 |
+
height: 6px;
|
| 328 |
+
border-radius: 50%;
|
| 329 |
+
background: #c9a961;
|
| 330 |
+
animation: typing 1.4s infinite;
|
| 331 |
+
}
|
| 332 |
+
.typing-dots span:nth-child(2) { animation-delay: 0.2s; }
|
| 333 |
+
.typing-dots span:nth-child(3) { animation-delay: 0.4s; }
|
| 334 |
+
@keyframes typing {
|
| 335 |
+
0%, 60%, 100% { transform: translateY(0); opacity: 0.5; }
|
| 336 |
+
30% { transform: translateY(-8px); opacity: 1; }
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
/* Button styles */
|
| 340 |
+
.btn-primary {
|
| 341 |
+
background: linear-gradient(135deg, #c9a961 0%, #9c7d3a 100%);
|
| 342 |
+
color: #1a0d07;
|
| 343 |
+
font-weight: 800;
|
| 344 |
+
transition: all 0.3s ease;
|
| 345 |
+
box-shadow: 0 8px 20px -5px rgba(201, 169, 97, 0.4);
|
| 346 |
+
}
|
| 347 |
+
.btn-primary:hover {
|
| 348 |
+
background: linear-gradient(135deg, #e4c97a 0%, #c9a961 100%);
|
| 349 |
+
transform: translateY(-2px);
|
| 350 |
+
box-shadow: 0 12px 28px -5px rgba(201, 169, 97, 0.6);
|
| 351 |
+
}
|
| 352 |
+
.btn-primary:disabled {
|
| 353 |
+
opacity: 0.5;
|
| 354 |
+
cursor: not-allowed;
|
| 355 |
+
transform: none;
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
.btn-ghost {
|
| 359 |
+
background: rgba(201, 169, 97, 0.08);
|
| 360 |
+
color: #f0dca3;
|
| 361 |
+
border: 1px solid rgba(201, 169, 97, 0.2);
|
| 362 |
+
transition: all 0.3s ease;
|
| 363 |
+
}
|
| 364 |
+
.btn-ghost:hover {
|
| 365 |
+
background: rgba(201, 169, 97, 0.18);
|
| 366 |
+
border-color: rgba(201, 169, 97, 0.45);
|
| 367 |
+
}
|
| 368 |
+
|
| 369 |
+
.btn-rug {
|
| 370 |
+
background: linear-gradient(135deg, #8b1e3f 0%, #5a1028 100%);
|
| 371 |
+
color: #f5e6d3;
|
| 372 |
+
border: 1px solid rgba(201, 169, 97, 0.3);
|
| 373 |
+
transition: all 0.3s ease;
|
| 374 |
+
}
|
| 375 |
+
.btn-rug:hover {
|
| 376 |
+
background: linear-gradient(135deg, #c23d5f 0%, #8b1e3f 100%);
|
| 377 |
+
border-color: rgba(201, 169, 97, 0.6);
|
| 378 |
+
transform: translateY(-1px);
|
| 379 |
+
}
|
| 380 |
+
|
| 381 |
+
.btn-turk {
|
| 382 |
+
background: linear-gradient(135deg, #0d9488 0%, #134e4a 100%);
|
| 383 |
+
color: #f5e6d3;
|
| 384 |
+
border: 1px solid rgba(45, 212, 191, 0.3);
|
| 385 |
+
transition: all 0.3s ease;
|
| 386 |
+
box-shadow: 0 6px 16px -4px rgba(13, 148, 136, 0.4);
|
| 387 |
+
}
|
| 388 |
+
.btn-turk:hover {
|
| 389 |
+
background: linear-gradient(135deg, #2dd4bf 0%, #0d9488 100%);
|
| 390 |
+
border-color: rgba(94, 234, 212, 0.6);
|
| 391 |
+
transform: translateY(-1px);
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
+
/* Drop zone */
|
| 395 |
+
.drop-zone {
|
| 396 |
+
position: relative;
|
| 397 |
+
border: 2px dashed rgba(201, 169, 97, 0.3);
|
| 398 |
+
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
| 399 |
+
}
|
| 400 |
+
.drop-zone:hover, .drop-zone.drag-over {
|
| 401 |
+
border-color: rgba(201, 169, 97, 0.7);
|
| 402 |
+
background: rgba(201, 169, 97, 0.06);
|
| 403 |
+
transform: scale(1.005);
|
| 404 |
+
}
|
| 405 |
+
.drop-zone::before {
|
| 406 |
+
content: '';
|
| 407 |
+
position: absolute;
|
| 408 |
+
inset: 6px;
|
| 409 |
+
border: 1px dashed rgba(201, 169, 97, 0.15);
|
| 410 |
+
border-radius: inherit;
|
| 411 |
+
pointer-events: none;
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
/* Order cards */
|
| 415 |
+
.order-card {
|
| 416 |
+
background: linear-gradient(135deg, rgba(45, 24, 16, 0.6), rgba(26, 13, 7, 0.5));
|
| 417 |
+
border: 1px solid rgba(201, 169, 97, 0.2);
|
| 418 |
+
transition: all 0.3s ease;
|
| 419 |
+
}
|
| 420 |
+
.order-card:hover {
|
| 421 |
+
border-color: rgba(201, 169, 97, 0.5);
|
| 422 |
+
box-shadow: 0 12px 30px -10px rgba(139, 30, 63, 0.3);
|
| 423 |
+
transform: translateY(-2px);
|
| 424 |
+
}
|
| 425 |
+
|
| 426 |
+
/* Table rows */
|
| 427 |
+
.menu-row {
|
| 428 |
+
transition: all 0.3s ease;
|
| 429 |
+
}
|
| 430 |
+
.menu-row:hover {
|
| 431 |
+
background: rgba(201, 169, 97, 0.05);
|
| 432 |
+
}
|
| 433 |
+
|
| 434 |
+
/* Input focus */
|
| 435 |
+
input:focus, textarea:focus, select:focus {
|
| 436 |
+
outline: none;
|
| 437 |
+
border-color: rgba(201, 169, 97, 0.6);
|
| 438 |
+
box-shadow: 0 0 0 3px rgba(201, 169, 97, 0.15);
|
| 439 |
+
}
|
| 440 |
+
|
| 441 |
+
/* Checkbox custom */
|
| 442 |
+
input[type="checkbox"] {
|
| 443 |
+
appearance: none;
|
| 444 |
+
width: 18px;
|
| 445 |
+
height: 18px;
|
| 446 |
+
border: 2px solid rgba(201, 169, 97, 0.4);
|
| 447 |
+
border-radius: 4px;
|
| 448 |
+
background: rgba(45, 24, 16, 0.5);
|
| 449 |
+
cursor: pointer;
|
| 450 |
+
position: relative;
|
| 451 |
+
transition: all 0.2s ease;
|
| 452 |
+
}
|
| 453 |
+
input[type="checkbox"]:checked {
|
| 454 |
+
background: linear-gradient(135deg, #0d9488, #134e4a);
|
| 455 |
+
border-color: #2dd4bf;
|
| 456 |
+
}
|
| 457 |
+
input[type="checkbox"]:checked::after {
|
| 458 |
+
content: '✓';
|
| 459 |
+
position: absolute;
|
| 460 |
+
top: 50%;
|
| 461 |
+
left: 50%;
|
| 462 |
+
transform: translate(-50%, -50%);
|
| 463 |
+
color: #f5e6d3;
|
| 464 |
+
font-size: 14px;
|
| 465 |
+
font-weight: bold;
|
| 466 |
+
}
|
| 467 |
+
|
| 468 |
+
/* Live indicator */
|
| 469 |
+
.live-dot {
|
| 470 |
+
display: inline-block;
|
| 471 |
+
width: 8px;
|
| 472 |
+
height: 8px;
|
| 473 |
+
border-radius: 50%;
|
| 474 |
+
background: #10b981;
|
| 475 |
+
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7);
|
| 476 |
+
animation: live-pulse 2s infinite;
|
| 477 |
+
}
|
| 478 |
+
@keyframes live-pulse {
|
| 479 |
+
0% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7); }
|
| 480 |
+
70% { box-shadow: 0 0 0 10px rgba(16, 185, 129, 0); }
|
| 481 |
+
100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); }
|
| 482 |
+
}
|
| 483 |
+
|
| 484 |
+
/* Section title */
|
| 485 |
+
.section-title {
|
| 486 |
+
font-family: 'Lalezar', serif;
|
| 487 |
+
letter-spacing: 0.02em;
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
/* Badge */
|
| 491 |
+
.badge-live {
|
| 492 |
+
background: linear-gradient(135deg, rgba(16, 185, 129, 0.15), rgba(6, 95, 70, 0.2));
|
| 493 |
+
border: 1px solid rgba(52, 211, 153, 0.4);
|
| 494 |
+
color: #34d399;
|
| 495 |
+
}
|
| 496 |
+
|
| 497 |
+
.badge-table {
|
| 498 |
+
background: linear-gradient(135deg, rgba(201, 169, 97, 0.15), rgba(139, 30, 63, 0.15));
|
| 499 |
+
border: 1px solid rgba(201, 169, 97, 0.4);
|
| 500 |
+
color: #e4c97a;
|
| 501 |
+
}
|
| 502 |
+
|
| 503 |
+
/* Table styling */
|
| 504 |
+
.admin-table thead {
|
| 505 |
+
background: linear-gradient(90deg, rgba(139, 30, 63, 0.25), rgba(201, 169, 97, 0.15));
|
| 506 |
+
}
|
| 507 |
+
.admin-table th {
|
| 508 |
+
font-family: 'Lalezar', serif;
|
| 509 |
+
color: #e4c97a;
|
| 510 |
+
font-size: 11px;
|
| 511 |
+
}
|
| 512 |
+
.admin-table td, .admin-table th {
|
| 513 |
+
border-bottom: 1px solid rgba(201, 169, 97, 0.1);
|
| 514 |
+
}
|
| 515 |
+
|
| 516 |
+
/* Mobile optimizations */
|
| 517 |
+
@media (max-width: 640px) {
|
| 518 |
+
.chat-markdown { font-size: 0.92rem; }
|
| 519 |
+
}
|
| 520 |
</style>
|
| 521 |
</head>
|
| 522 |
+
<body class="min-h-screen flex flex-col font-sans selection:bg-ochre selection:text-bazaar-950 overflow-x-hidden antialiased">
|
| 523 |
|
| 524 |
+
<!-- Ambient Background -->
|
| 525 |
+
<div class="fixed inset-0 overflow-hidden pointer-events-none z-0">
|
| 526 |
+
<div class="absolute inset-0 bg-gradient-to-br from-bazaar-950 via-bazaar-900 to-azure-950"></div>
|
| 527 |
+
<div class="absolute inset-0 bg-pattern opacity-40"></div>
|
| 528 |
+
<div class="absolute -top-40 -right-40 w-[600px] h-[600px] rounded-full bg-rug/10 blur-[140px] animate-float"></div>
|
| 529 |
+
<div class="absolute -bottom-40 -left-40 w-[600px] h-[600px] rounded-full bg-turk/10 blur-[140px] animate-float" style="animation-delay: -3s;"></div>
|
| 530 |
+
<div class="absolute top-1/3 left-1/4 w-[400px] h-[400px] rounded-full bg-ochre/8 blur-[120px] animate-shimmer"></div>
|
| 531 |
</div>
|
| 532 |
|
| 533 |
<!-- Header -->
|
| 534 |
+
<header class="border-b border-ochre/20 bg-bazaar-900/70 backdrop-blur-2xl sticky top-0 z-50 px-4 py-3 md:px-8 shadow-lg">
|
| 535 |
+
<div class="max-w-7xl mx-auto flex items-center justify-between gap-4">
|
| 536 |
<div class="flex items-center gap-3">
|
| 537 |
+
<div class="relative">
|
| 538 |
+
<div class="w-11 h-11 arch-shape bg-gradient-to-br from-rug via-rug-dark to-bazaar-900 border-2 border-ochre/50 flex items-center justify-center shadow-carpet">
|
| 539 |
+
<svg class="w-6 h-6 text-ochre-light" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
| 540 |
+
<path d="M17 8h1a4 4 0 1 1 0 8h-1" stroke-linecap="round"/>
|
| 541 |
+
<path d="M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z" stroke-linecap="round"/>
|
| 542 |
+
<line x1="6" y1="2" x2="6" y2="4" stroke-linecap="round"/>
|
| 543 |
+
<line x1="10" y1="2" x2="10" y2="4" stroke-linecap="round"/>
|
| 544 |
+
<line x1="14" y1="2" x2="14" y2="4" stroke-linecap="round"/>
|
| 545 |
+
</svg>
|
| 546 |
+
</div>
|
| 547 |
</div>
|
| 548 |
<div>
|
| 549 |
+
<h1 class="section-title text-base md:text-lg text-ochre-light tracking-wide flex items-center gap-2">
|
| 550 |
+
<span class="text-rug-light">Cafe AI</span>
|
| 551 |
+
<span class="text-cream/40">|</span>
|
| 552 |
+
<span class="text-cream">پنل مدیریت</span>
|
| 553 |
</h1>
|
| 554 |
+
<p class="text-[10px] text-turk-light/80 font-medium mt-0.5">پایش برخط سفارشات و منوی هوشمند</p>
|
| 555 |
</div>
|
| 556 |
</div>
|
| 557 |
+
|
| 558 |
+
<div class="flex items-center gap-2">
|
| 559 |
+
<span class="badge-live inline-flex items-center gap-2 px-3 py-1.5 rounded-full text-[10px] font-bold">
|
| 560 |
+
<span class="live-dot"></span>
|
| 561 |
نظارت زنده
|
| 562 |
</span>
|
| 563 |
</div>
|
|
|
|
| 566 |
|
| 567 |
<!-- Main grid -->
|
| 568 |
<main class="flex-1 max-w-7xl w-full mx-auto p-4 md:p-6 lg:p-8 grid grid-cols-1 lg:grid-cols-12 gap-6 z-10 relative">
|
| 569 |
+
|
| 570 |
<!-- Right column: orders + current menu + menu upload (7/12) -->
|
| 571 |
<section class="lg:col-span-7 flex flex-col gap-6">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 572 |
|
| 573 |
+
<!-- Live orders monitor -->
|
| 574 |
+
<div class="glass-card rounded-3xl p-5 md:p-6 shadow-xl relative overflow-hidden">
|
| 575 |
+
<div class="absolute inset-0 bg-pattern-fine opacity-20 pointer-events-none"></div>
|
| 576 |
+
<div class="relative">
|
| 577 |
+
<div class="flex items-center justify-between mb-4 border-b border-ochre/20 pb-3">
|
| 578 |
+
<div class="flex items-center gap-3">
|
| 579 |
+
<div class="w-9 h-9 rounded-xl bg-rug/20 border border-rug/40 flex items-center justify-center">
|
| 580 |
+
<svg class="w-5 h-5 text-rug-light" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
| 581 |
+
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"/>
|
| 582 |
+
</svg>
|
| 583 |
+
</div>
|
| 584 |
+
<div>
|
| 585 |
+
<h2 class="section-title text-base md:text-lg text-ochre-light flex items-center gap-2">
|
| 586 |
+
سفارشات جدید آشپزخانه
|
| 587 |
+
<span class="w-2 h-2 rounded-full bg-rug-light animate-pulse"></span>
|
| 588 |
+
</h2>
|
| 589 |
+
<p class="text-[10px] text-cream/50 mt-0.5">لیست سفارشات در صف آمادهسازی</p>
|
| 590 |
+
</div>
|
| 591 |
+
</div>
|
| 592 |
+
<button onclick="fetchActiveOrders()" class="btn-ghost text-[10px] flex items-center gap-1.5 px-3 py-1.5 rounded-xl">
|
| 593 |
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
| 594 |
+
<path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 1121.21 15H15"/>
|
| 595 |
</svg>
|
| 596 |
+
بروزرسانی
|
|
|
|
|
|
|
|
|
|
| 597 |
</button>
|
| 598 |
</div>
|
| 599 |
+
<div id="ordersContainer" class="space-y-3 max-h-[400px] overflow-y-auto pr-1">
|
| 600 |
+
<div class="text-center py-12 text-xs text-ochre/60">در حال بارگذاری لیست سفارشات جاری...</div>
|
| 601 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 602 |
</div>
|
| 603 |
</div>
|
| 604 |
|
| 605 |
+
<!-- Current Menu -->
|
| 606 |
+
<div class="glass-card rounded-3xl p-5 md:p-6 space-y-4 shadow-xl relative overflow-hidden">
|
| 607 |
+
<div class="absolute inset-0 bg-arch opacity-15 pointer-events-none"></div>
|
| 608 |
+
<div class="relative">
|
| 609 |
+
<div class="flex items-center justify-between border-b border-ochre/20 pb-3 mb-3">
|
| 610 |
+
<div class="flex items-center gap-3">
|
| 611 |
+
<div class="w-9 h-9 rounded-xl bg-ochre/20 border border-ochre/40 flex items-center justify-center">
|
| 612 |
+
<svg class="w-5 h-5 text-ochre-light" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
| 613 |
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"/>
|
| 614 |
+
</svg>
|
| 615 |
+
</div>
|
| 616 |
+
<div>
|
| 617 |
+
<h2 class="section-title text-base md:text-lg text-ochre-light">مدیریت منوی فعال</h2>
|
| 618 |
+
<p class="text-[10px] text-cream/50 mt-0.5">کنترل و ویرایش اقلام قابل سفارش</p>
|
| 619 |
+
</div>
|
| 620 |
+
</div>
|
| 621 |
+
<div class="flex gap-2">
|
| 622 |
+
<button onclick="fetchCurrentMenu()" class="btn-ghost text-[10px] flex items-center gap-1 px-3 py-1.5 rounded-xl">
|
| 623 |
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
| 624 |
+
<path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 1121.21 15H15"/>
|
| 625 |
+
</svg>
|
| 626 |
+
بازنشانی
|
| 627 |
+
</button>
|
| 628 |
+
<button onclick="addMenuItemRow()" class="btn-turk px-4 py-1.5 rounded-xl text-[10px] font-extrabold flex items-center gap-1">
|
| 629 |
+
<span>+ افزودن دستی</span>
|
| 630 |
+
</button>
|
| 631 |
+
</div>
|
| 632 |
</div>
|
| 633 |
+
<p class="text-[11px] text-cream/70 leading-relaxed mb-4">
|
| 634 |
+
لیست تمام اقلام منوی کافه. میتوانید هر آیتم را ویرایش، حذف یا آیتم جدید اضافه کنید. تغییرات بلافاصله روی سامانه مشتریان اعمال میشود.
|
| 635 |
+
</p>
|
| 636 |
+
<div class="overflow-x-auto max-h-[400px] border border-ochre/20 rounded-2xl bg-bazaar-950/50">
|
| 637 |
+
<table class="min-w-full admin-table text-[11px]" id="currentMenuTable">
|
| 638 |
+
<thead class="sticky top-0">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 639 |
<tr>
|
| 640 |
+
<th class="px-3 py-2.5 text-right">نام محصول</th>
|
| 641 |
+
<th class="px-3 py-2.5 text-right hidden md:table-cell">توضیحات</th>
|
| 642 |
+
<th class="px-3 py-2.5 text-right w-20">قیمت</th>
|
| 643 |
+
<th class="px-3 py-2.5 text-center w-16">موجود</th>
|
| 644 |
+
<th class="px-3 py-2.5 text-center w-24">عملیات</th>
|
| 645 |
</tr>
|
| 646 |
</thead>
|
| 647 |
+
<tbody id="menuTableBody" class="divide-y divide-ochre/10">
|
| 648 |
+
<!-- filled by JS -->
|
| 649 |
</tbody>
|
| 650 |
</table>
|
| 651 |
</div>
|
| 652 |
+
</div>
|
| 653 |
+
</div>
|
| 654 |
+
|
| 655 |
+
<!-- Menu extraction & upload -->
|
| 656 |
+
<div class="glass-card rounded-3xl p-5 md:p-6 space-y-4 shadow-xl relative overflow-hidden">
|
| 657 |
+
<div class="absolute inset-0 bg-pattern-fine opacity-20 pointer-events-none"></div>
|
| 658 |
+
<div class="relative">
|
| 659 |
+
<div class="flex items-center gap-3 border-b border-ochre/20 pb-3 mb-3">
|
| 660 |
+
<div class="w-9 h-9 rounded-xl bg-turk/20 border border-turk/40 flex items-center justify-center">
|
| 661 |
+
<svg class="w-5 h-5 text-turk-light" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
| 662 |
+
<path stroke-linecap="round" stroke-linejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/>
|
| 663 |
+
</svg>
|
| 664 |
+
</div>
|
| 665 |
+
<div>
|
| 666 |
+
<h2 class="section-title text-base md:text-lg text-ochre-light">استخراج خودکار منو با هوش مصنوعی</h2>
|
| 667 |
+
<p class="text-[10px] text-cream/50 mt-0.5">بارگذاری تصویر یا فایل PDF منو</p>
|
| 668 |
+
</div>
|
| 669 |
+
</div>
|
| 670 |
+
<p class="text-[11px] text-cream/70 leading-relaxed mb-4">
|
| 671 |
+
تصویر منو یا فاکتور جدید کافه را بارگذاری کنید. هوش مصنوعی نیلا متون و قیمتها را استخراج کرده و جهت تایید نهایی ساختاربندی میکند.
|
| 672 |
+
</p>
|
| 673 |
+
|
| 674 |
+
<!-- Drag & drop zone -->
|
| 675 |
+
<div id="dropZone"
|
| 676 |
+
class="drop-zone rounded-2xl p-8 md:p-10 text-center cursor-pointer"
|
| 677 |
+
onclick="document.getElementById('menuFileInput').click()"
|
| 678 |
+
ondragover="handleDragOver(event)"
|
| 679 |
+
ondragleave="handleDragLeave(event)"
|
| 680 |
+
ondrop="handleDrop(event)">
|
| 681 |
+
<input type="file" id="menuFileInput" class="hidden" accept="image/*,application/pdf" onchange="uploadMenuFile(event)">
|
| 682 |
+
<div class="flex flex-col items-center gap-3">
|
| 683 |
+
<div class="w-16 h-16 rounded-2xl bg-gradient-to-br from-ochre/20 to-rug/15 border border-ochre/30 flex items-center justify-center">
|
| 684 |
+
<svg id="uploadIcon" class="w-8 h-8 text-ochre-light transition-all duration-300" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
| 685 |
+
<path stroke-linecap="round" stroke-linejoin="round" d="M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z"/>
|
| 686 |
+
</svg>
|
| 687 |
+
</div>
|
| 688 |
+
<span class="text-sm text-cream font-medium">فایل یا تصویر منو را بکشید یا انتخاب کنید</span>
|
| 689 |
+
<span class="text-[10px] text-ochre/60 uppercase tracking-widest">فرمتهای تصویری یا PDF</span>
|
| 690 |
+
</div>
|
| 691 |
+
</div>
|
| 692 |
+
|
| 693 |
+
<div id="uploadLoader" class="hidden text-center py-6 bg-bazaar-950/70 rounded-2xl border border-turk/30 mt-4">
|
| 694 |
+
<div class="inline-block animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-ochre mb-2"></div>
|
| 695 |
+
<p class="text-[10px] text-ochre-light font-extrabold">نیلا در حال ساختارسازی و آنالیز متون منوی جدید...</p>
|
| 696 |
+
</div>
|
| 697 |
+
|
| 698 |
+
<div id="menuEditorContainer" class="hidden space-y-4 border-t border-ochre/15 pt-4 mt-4">
|
| 699 |
+
<h3 class="text-xs font-bold text-ochre-light flex items-center gap-1.5 section-title">
|
| 700 |
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
| 701 |
+
<path stroke-linecap="round" stroke-linejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
| 702 |
+
</svg>
|
| 703 |
+
بازبینی اقلام منو پیش از انتشار زنده
|
| 704 |
+
</h3>
|
| 705 |
+
<div class="overflow-x-auto max-h-[250px] border border-ochre/20 rounded-2xl bg-bazaar-950/50">
|
| 706 |
+
<table class="min-w-full admin-table text-[11px]">
|
| 707 |
+
<thead>
|
| 708 |
+
<tr>
|
| 709 |
+
<th class="px-3 py-2.5 text-right">عنوان محصول</th>
|
| 710 |
+
<th class="px-3 py-2.5 text-right">توضیحات کوتاه</th>
|
| 711 |
+
<th class="px-3 py-2.5 text-right w-24">قیمت (تومان)</th>
|
| 712 |
+
<th class="px-3 py-2.5 text-center w-16">عملیات</th>
|
| 713 |
+
</tr>
|
| 714 |
+
</thead>
|
| 715 |
+
<tbody id="menuEditorTableBody" class="divide-y divide-ochre/10">
|
| 716 |
+
<!-- dynamic rows -->
|
| 717 |
+
</tbody>
|
| 718 |
+
</table>
|
| 719 |
+
</div>
|
| 720 |
+
<div class="flex justify-end gap-2 flex-wrap">
|
| 721 |
+
<button onclick="addEmptyMenuRow()" class="btn-ghost px-4 py-2 rounded-xl text-[10px]">
|
| 722 |
+
+ افزودن دستی محصول جدید
|
| 723 |
+
</button>
|
| 724 |
+
<button onclick="saveAndPublishMenu()" class="btn-turk px-5 py-2.5 rounded-xl text-[11px] font-extrabold flex items-center gap-2">
|
| 725 |
+
ثبت و همگامسازی زنده منو
|
| 726 |
+
</button>
|
| 727 |
+
</div>
|
| 728 |
</div>
|
| 729 |
</div>
|
| 730 |
</div>
|
| 731 |
</section>
|
| 732 |
|
| 733 |
<!-- Left column: Admin chat assistant (5/12) -->
|
| 734 |
+
<section class="lg:col-span-5 flex flex-col glass-card-azure rounded-3xl h-[800px] lg:h-auto overflow-hidden shadow-xl relative">
|
| 735 |
+
<div class="absolute inset-0 bg-arch opacity-15 pointer-events-none"></div>
|
| 736 |
+
|
| 737 |
+
<div class="relative px-5 py-4 border-b border-turk/25 flex items-center justify-between bg-gradient-to-l from-azure-950/60 to-azure-900/40">
|
| 738 |
<div class="flex items-center gap-3">
|
| 739 |
+
<div class="w-11 h-11 rounded-xl bg-gradient-to-br from-turk/30 to-azure-700/30 border border-turk/40 flex items-center justify-center shadow-mosque">
|
| 740 |
+
<svg class="w-6 h-6 text-turk-glow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
| 741 |
<path d="M12 2a10 10 0 0 1 10 10c0 5.523-4.477 10-10 10S2 17.523 2 12A10 10 0 0 1 12 2z"/>
|
| 742 |
+
<path d="M12 8v4l3 3" stroke-linecap="round"/>
|
| 743 |
</svg>
|
| 744 |
</div>
|
| 745 |
<div>
|
| 746 |
+
<h3 class="section-title text-sm text-ochre-light tracking-wide">دستیار هوشمند ادمین</h3>
|
| 747 |
+
<p class="text-[10px] text-turk-light/80 font-medium flex items-center gap-1.5">
|
| 748 |
+
<span class="w-1.5 h-1.5 rounded-full bg-turk-light animate-pulse"></span>
|
| 749 |
+
کنترل همگام موجودی و منو با زبان طبیعی
|
| 750 |
+
</p>
|
| 751 |
</div>
|
| 752 |
</div>
|
| 753 |
</div>
|
| 754 |
|
| 755 |
+
<div id="chatMessages" class="relative flex-1 overflow-y-auto p-4 space-y-4">
|
| 756 |
<div class="flex gap-2.5 max-w-[85%]">
|
| 757 |
+
<div class="w-8 h-8 rounded-xl bg-gradient-to-br from-turk/20 to-azure-700/20 border border-turk/30 flex items-center justify-center flex-shrink-0">
|
| 758 |
+
<span class="text-ochre-light font-display text-[11px]">ن</span>
|
| 759 |
</div>
|
| 760 |
+
<div class="bg-bazaar-950/70 border border-ochre/20 text-cream text-xs rounded-2xl rounded-tr-none px-3.5 py-2.5 leading-relaxed shadow-inner-soft chat-markdown">
|
| 761 |
+
سلام همکار گرامی! من <strong>نیلا</strong> هستم، دستیار مدیریت منوی <em>Cafe AI</em>. شما میتوانید از همینجا اقلام منو را با گفتگوی ساده کنترل کنید؛ برای مثال بنویسید: «کروسان ساده تمام شد» یا «چای ماسالا را موجود کن» تا سریعاً تغییرات روی سیستم مشتریان نیز اعمال گردد.
|
| 762 |
</div>
|
| 763 |
</div>
|
| 764 |
</div>
|
| 765 |
|
| 766 |
+
<div id="chatLoader" class="hidden relative px-4 py-3 flex items-center justify-between bg-azure-950/70 text-[10px] text-turk-light font-semibold border-t border-turk/20">
|
| 767 |
+
<div class="flex items-center gap-2.5">
|
| 768 |
+
<div class="typing-dots flex gap-1">
|
| 769 |
+
<span></span><span></span><span></span>
|
|
|
|
|
|
|
| 770 |
</div>
|
| 771 |
+
<span>نیلا در حال پردازش دستور مدیریت...</span>
|
| 772 |
</div>
|
| 773 |
+
<button onclick="stopAdminChat()" class="px-3 py-1.5 bg-rug-dark/40 border border-rug/40 hover:bg-rug/40 rounded-lg text-[10px] text-rug-light font-bold transition-all">
|
| 774 |
+
توقف
|
| 775 |
+
</button>
|
| 776 |
</div>
|
| 777 |
|
| 778 |
+
<form id="chatForm" onsubmit="sendAdminMessage(event)" class="relative p-3 border-t border-turk/20 bg-bazaar-950/60 flex gap-2">
|
| 779 |
+
<input type="text" id="chatInput" placeholder="تغییر وضعیت یا ویرایش منو را متنی بنویسید..." autocomplete="off"
|
| 780 |
+
class="flex-1 bg-azure-950/60 border border-azure-700/40 text-xs text-cream placeholder-ochre/40 rounded-xl px-4 py-3 focus:outline-none focus:border-ochre/60 transition-all duration-300">
|
| 781 |
+
<button type="submit" id="sendBtn" class="btn-primary px-4 py-3 rounded-xl text-xs flex items-center justify-center flex-shrink-0">
|
| 782 |
+
<svg class="w-5 h-5 rotate-180" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
|
| 783 |
+
<path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"/>
|
| 784 |
</svg>
|
| 785 |
</button>
|
| 786 |
</form>
|
| 787 |
</section>
|
| 788 |
</main>
|
| 789 |
|
| 790 |
+
<footer class="border-t border-ochre/15 py-4 text-center text-[10px] text-cream/40 bg-bazaar-950/60 backdrop-blur">
|
| 791 |
طراحی و توسعه توسط الگوریتم داده نسترن | با الهام از فرهنگ تبریز © ۲۰۲۶
|
| 792 |
</footer>
|
| 793 |
|
|
|
|
| 796 |
marked.setOptions({ sanitize: true, breaks: true });
|
| 797 |
|
| 798 |
let chatHistory = [
|
| 799 |
+
{ role: 'model', content: 'سلام همکار گرامی! من نیلا هستم، دستیار مدیریت منوی Cafe AI. شما میتوانید از همینجا اقلام منو را با گفتگوی ساده کنترل کنید؛ برای مثال بنویسید: «کروسان ساده تمام شد» یا «چای ماسالا را موجود کن» تا سریعاً تغییرات روی سیستم مشتریان نیز اعمال گردد.' }
|
| 800 |
];
|
| 801 |
let abortController = null;
|
| 802 |
let isGenerating = false;
|
|
|
|
| 807 |
setInterval(fetchActiveOrders, 10000);
|
| 808 |
});
|
| 809 |
|
| 810 |
+
// ==================== Order functions ====================
|
| 811 |
async function fetchActiveOrders() {
|
| 812 |
const container = document.getElementById('ordersContainer');
|
| 813 |
try {
|
|
|
|
| 816 |
if (response.ok) {
|
| 817 |
if (orders.length === 0) {
|
| 818 |
container.innerHTML = `
|
| 819 |
+
<div class="text-center py-16 text-ochre/60 border-2 border-dashed border-ochre/25 rounded-2xl bg-bazaar-950/30">
|
| 820 |
+
<svg class="w-10 h-10 text-ochre/40 mx-auto mb-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
| 821 |
+
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
| 822 |
</svg>
|
| 823 |
+
<p class="text-sm font-medium">در حال حاضر سفارش معلقی در صف آشپزخانه نیست</p>
|
| 824 |
+
<p class="text-[10px] text-cream/40 mt-1">سفارشات جدید به صورت خودکار بهروزرسانی میشوند</p>
|
| 825 |
</div>
|
| 826 |
`;
|
| 827 |
return;
|
|
|
|
| 831 |
let itemsHtml = '';
|
| 832 |
order.items.forEach(item => {
|
| 833 |
itemsHtml += `
|
| 834 |
+
<div class="flex items-center justify-between text-[11px] bg-bazaar-950/60 px-3 py-2 rounded-lg border border-ochre/20 group">
|
| 835 |
+
<span class="text-cream font-bold flex items-center gap-2">
|
| 836 |
+
<span class="w-1 h-5 bg-gradient-to-b from-rug to-ochre rounded-full"></span>
|
| 837 |
+
${escapeHtml(item.name)}
|
| 838 |
+
</span>
|
| 839 |
+
<span class="text-ochre-light bg-ochre/10 px-2.5 py-0.5 rounded-md font-display text-[10px] border border-ochre/25">
|
| 840 |
+
×${item.quantity}
|
| 841 |
+
</span>
|
| 842 |
</div>
|
| 843 |
`;
|
| 844 |
});
|
| 845 |
const timeString = new Date(order.timestamp * 1000).toLocaleTimeString('fa-IR', { hour: '2-digit', minute: '2-digit' });
|
| 846 |
const cardHtml = `
|
| 847 |
+
<div class="order-card rounded-2xl p-4 flex flex-col md:flex-row justify-between items-start md:items-center gap-3">
|
| 848 |
<div class="flex-1 space-y-2 w-full">
|
| 849 |
+
<div class="flex items-center justify-between md:justify-start gap-3 w-full flex-wrap">
|
| 850 |
+
<span class="badge-table text-xs font-bold px-3 py-1 rounded-lg flex items-center gap-1.5">
|
| 851 |
+
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
| 852 |
+
<path stroke-linecap="round" stroke-linejoin="round" d="M3 10h18M3 14h18m-9-4v8m-7 0h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"/>
|
| 853 |
+
</svg>
|
| 854 |
+
میز شماره ${order.table}
|
| 855 |
+
</span>
|
| 856 |
+
<span class="text-[10px] text-turk-light/70 font-medium flex items-center gap-1">
|
| 857 |
+
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
| 858 |
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
| 859 |
+
</svg>
|
| 860 |
+
${timeString}
|
| 861 |
+
</span>
|
| 862 |
</div>
|
| 863 |
<div class="grid grid-cols-1 sm:grid-cols-2 gap-1.5 mt-2">
|
| 864 |
${itemsHtml}
|
| 865 |
</div>
|
| 866 |
</div>
|
| 867 |
+
<button onclick="completeOrder(${order.id})" class="w-full md:w-auto btn-ghost hover:bg-emerald-dark/40 hover:border-emerald-light/50 hover:text-emerald-light px-4 py-2.5 rounded-xl text-[10px] font-bold flex items-center justify-center gap-1.5 flex-shrink-0 group">
|
| 868 |
+
<svg class="w-4 h-4 transition-transform group-hover:scale-110" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
|
| 869 |
+
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>
|
| 870 |
</svg>
|
| 871 |
تحویل نهایی شد
|
| 872 |
</button>
|
|
|
|
| 875 |
container.insertAdjacentHTML('beforeend', cardHtml);
|
| 876 |
});
|
| 877 |
} else {
|
| 878 |
+
container.innerHTML = `<div class="text-center py-6 text-rug-light text-xs">خطا در دریافت لیست سفارشات آشپزخانه.</div>`;
|
| 879 |
}
|
| 880 |
} catch (err) {
|
| 881 |
+
container.innerHTML = `<div class="text-center py-6 text-rug-light text-xs">عدم برقراری ارتباط زنده با سرور: ${escapeHtml(err.message)}</div>`;
|
| 882 |
}
|
| 883 |
}
|
| 884 |
|
|
|
|
| 900 |
}
|
| 901 |
}
|
| 902 |
|
| 903 |
+
// ==================== Menu CRUD ====================
|
| 904 |
async function fetchCurrentMenu() {
|
| 905 |
const tbody = document.getElementById('menuTableBody');
|
| 906 |
+
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-8 text-ochre/60 text-xs">در حال بارگذاری منو...</td></tr>';
|
| 907 |
try {
|
| 908 |
const res = await fetch('/api/admin/menu');
|
| 909 |
const menu = await res.json();
|
| 910 |
if (res.ok && Array.isArray(menu)) {
|
| 911 |
renderMenuTable(menu);
|
| 912 |
} else {
|
| 913 |
+
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-8 text-rug-light text-xs">خطا در بارگذاری منو</td></tr>';
|
| 914 |
}
|
| 915 |
} catch (e) {
|
| 916 |
+
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-8 text-rug-light text-xs">عدم اتصال به سرور</td></tr>';
|
| 917 |
}
|
| 918 |
}
|
| 919 |
|
|
|
|
| 921 |
const tbody = document.getElementById('menuTableBody');
|
| 922 |
tbody.innerHTML = '';
|
| 923 |
if (menu.length === 0) {
|
| 924 |
+
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-8 text-ochre/60 text-xs">منو خالی است. آیتم جدید اضافه کنید.</td></tr>';
|
| 925 |
return;
|
| 926 |
}
|
| 927 |
menu.forEach(item => {
|
| 928 |
const row = document.createElement('tr');
|
| 929 |
+
row.className = 'menu-row';
|
| 930 |
row.dataset.id = item.id;
|
| 931 |
row.innerHTML = `
|
| 932 |
+
<td class="px-2 py-2">
|
| 933 |
+
<span class="view-mode font-bold text-cream">${escapeHtml(item.name)}</span>
|
| 934 |
+
<input type="text" class="edit-mode hidden w-full bg-azure-950/60 border border-azure-700/40 rounded px-2 py-1 text-cream text-[11px]" value="${escapeHtml(item.name)}" data-field="name">
|
| 935 |
</td>
|
| 936 |
+
<td class="px-2 py-2 hidden md:table-cell">
|
| 937 |
+
<span class="view-mode text-cream/60 text-[10px]">${escapeHtml(item.description || '-')}</span>
|
| 938 |
+
<input type="text" class="edit-mode hidden w-full bg-azure-950/60 border border-azure-700/40 rounded px-2 py-1 text-cream text-[11px]" value="${escapeHtml(item.description || '')}" data-field="description">
|
| 939 |
</td>
|
| 940 |
+
<td class="px-2 py-2">
|
| 941 |
+
<span class="view-mode text-ochre-light font-medium">${escapeHtml(item.price || '-')}</span>
|
| 942 |
+
<input type="text" class="edit-mode hidden w-full bg-azure-950/60 border border-azure-700/40 rounded px-2 py-1 text-cream text-[11px]" value="${escapeHtml(item.price || '')}" data-field="price">
|
| 943 |
</td>
|
| 944 |
+
<td class="px-2 py-2 text-center">
|
| 945 |
+
<span class="view-mode text-[10px] ${item.available ? 'text-emerald-light' : 'text-rug-light'} font-bold">${item.available ? '✓ موجود' : '✗ ناموجود'}</span>
|
| 946 |
+
<div class="edit-mode hidden flex items-center justify-center">
|
| 947 |
+
<input type="checkbox" data-field="available" ${item.available ? 'checked' : ''}>
|
| 948 |
+
</div>
|
| 949 |
</td>
|
| 950 |
+
<td class="px-2 py-2 text-center">
|
| 951 |
<div class="view-mode flex gap-1 justify-center">
|
| 952 |
+
<button onclick="editMenuItem(this)" class="p-1.5 text-turk/70 hover:text-ochre-light transition-colors rounded-lg hover:bg-bazaar-800/50" title="ویرایش">
|
| 953 |
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
| 954 |
+
<path stroke-linecap="round" stroke-linejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
| 955 |
</svg>
|
| 956 |
</button>
|
| 957 |
+
<button onclick="deleteMenuItem(${item.id})" class="p-1.5 text-cream/30 hover:text-rug-light transition-colors rounded-lg hover:bg-bazaar-800/50" title="حذف">
|
| 958 |
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
| 959 |
+
<path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
| 960 |
</svg>
|
| 961 |
</button>
|
| 962 |
</div>
|
| 963 |
<div class="edit-mode hidden flex gap-1 justify-center">
|
| 964 |
+
<button onclick="saveMenuItem(this, ${item.id})" class="px-2.5 py-1 bg-emerald-dark/40 border border-emerald-light/40 text-emerald-light rounded-md text-[10px] font-bold hover:bg-emerald-dark/60">ذخیره</button>
|
| 965 |
+
<button onclick="cancelEdit(this)" class="px-2.5 py-1 bg-bazaar-800 border border-ochre/20 text-cream/60 rounded-md text-[10px] hover:border-ochre/40">لغو</button>
|
| 966 |
</div>
|
| 967 |
</td>
|
| 968 |
`;
|
|
|
|
| 987 |
}
|
| 988 |
|
| 989 |
function cancelEdit(btn) {
|
|
|
|
|
|
|
|
|
|
| 990 |
fetchCurrentMenu();
|
| 991 |
}
|
| 992 |
|
|
|
|
| 1039 |
}
|
| 1040 |
}
|
| 1041 |
|
| 1042 |
+
function addMenuItemRow() {
|
|
|
|
| 1043 |
const tbody = document.getElementById('menuTableBody');
|
| 1044 |
const row = document.createElement('tr');
|
| 1045 |
+
row.className = 'menu-row bg-bazaar-800/40';
|
| 1046 |
row.innerHTML = `
|
| 1047 |
+
<td class="px-2 py-2">
|
| 1048 |
+
<input type="text" class="w-full bg-azure-950/60 border border-turk/40 rounded px-2 py-1 text-cream text-[11px]" placeholder="نام محصول" data-field="name">
|
| 1049 |
</td>
|
| 1050 |
+
<td class="px-2 py-2 hidden md:table-cell">
|
| 1051 |
+
<input type="text" class="w-full bg-azure-950/60 border border-azure-700/40 rounded px-2 py-1 text-cream text-[11px]" placeholder="توضیحات" data-field="description">
|
| 1052 |
</td>
|
| 1053 |
+
<td class="px-2 py-2">
|
| 1054 |
+
<input type="text" class="w-full bg-azure-950/60 border border-azure-700/40 rounded px-2 py-1 text-cream text-[11px]" placeholder="قیمت" data-field="price">
|
| 1055 |
</td>
|
| 1056 |
+
<td class="px-2 py-2 text-center">
|
| 1057 |
<input type="checkbox" data-field="available" checked>
|
| 1058 |
</td>
|
| 1059 |
+
<td class="px-2 py-2 text-center">
|
| 1060 |
+
<button onclick="saveNewItem(this)" class="btn-turk px-3 py-1 rounded-md text-[10px]">افزودن</button>
|
| 1061 |
</td>
|
| 1062 |
`;
|
| 1063 |
tbody.appendChild(row);
|
|
|
|
| 1090 |
}
|
| 1091 |
}
|
| 1092 |
|
| 1093 |
+
// ==================== Menu extraction ====================
|
| 1094 |
function handleDragOver(e) {
|
| 1095 |
e.preventDefault();
|
| 1096 |
const dropZone = document.getElementById('dropZone');
|
| 1097 |
+
dropZone.classList.add('drag-over');
|
|
|
|
|
|
|
| 1098 |
}
|
| 1099 |
function handleDragLeave(e) {
|
| 1100 |
e.preventDefault();
|
| 1101 |
const dropZone = document.getElementById('dropZone');
|
| 1102 |
+
dropZone.classList.remove('drag-over');
|
|
|
|
|
|
|
| 1103 |
}
|
| 1104 |
function handleDrop(e) {
|
| 1105 |
e.preventDefault();
|
|
|
|
| 1144 |
tbody.innerHTML = '';
|
| 1145 |
menuItems.forEach((item, index) => {
|
| 1146 |
const row = `
|
| 1147 |
+
<tr class="menu-row">
|
| 1148 |
<td class="px-2 py-2">
|
| 1149 |
+
<input type="text" class="menu-name w-full bg-azure-950/60 border border-azure-700/40 rounded-lg px-2.5 py-1.5 text-cream focus:border-ochre/50 text-[11px]" value="${escapeHtml(item.name || '')}">
|
| 1150 |
</td>
|
| 1151 |
<td class="px-2 py-2">
|
| 1152 |
+
<input type="text" class="menu-desc w-full bg-azure-950/60 border border-azure-700/40 rounded-lg px-2.5 py-1.5 text-cream focus:border-ochre/50 text-[11px]" value="${escapeHtml(item.description || '')}">
|
| 1153 |
</td>
|
| 1154 |
<td class="px-2 py-2">
|
| 1155 |
+
<input type="text" class="menu-price w-full bg-azure-950/60 border border-azure-700/40 rounded-lg px-2.5 py-1.5 text-cream focus:border-ochre/50 text-[11px]" value="${escapeHtml(item.price || '')}">
|
| 1156 |
</td>
|
| 1157 |
<td class="px-2 py-2 text-center">
|
| 1158 |
+
<button onclick="removeMenuRow(this)" class="p-1.5 text-cream/30 hover:text-rug-light transition-colors rounded-lg hover:bg-bazaar-800/50">
|
| 1159 |
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
| 1160 |
+
<path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
| 1161 |
</svg>
|
| 1162 |
</button>
|
| 1163 |
</td>
|
|
|
|
| 1169 |
function addEmptyMenuRow() {
|
| 1170 |
const tbody = document.getElementById('menuEditorTableBody');
|
| 1171 |
const row = `
|
| 1172 |
+
<tr class="menu-row">
|
| 1173 |
<td class="px-2 py-2">
|
| 1174 |
+
<input type="text" class="menu-name w-full bg-azure-950/60 border border-azure-700/40 rounded-lg px-2.5 py-1.5 text-cream focus:border-ochre/50 text-[11px]" placeholder="مثال: لاته کارامل">
|
| 1175 |
</td>
|
| 1176 |
<td class="px-2 py-2">
|
| 1177 |
+
<input type="text" class="menu-desc w-full bg-azure-950/60 border border-azure-700/40 rounded-lg px-2.5 py-1.5 text-cream focus:border-ochre/50 text-[11px]" placeholder="ترکیب اسپرسو دبل، سس کارامل و فوم شیر">
|
| 1178 |
</td>
|
| 1179 |
<td class="px-2 py-2">
|
| 1180 |
+
<input type="text" class="menu-price w-full bg-azure-950/60 border border-azure-700/40 rounded-lg px-2.5 py-1.5 text-cream focus:border-ochre/50 text-[11px]" placeholder="۸۵,۰۰۰">
|
| 1181 |
</td>
|
| 1182 |
<td class="px-2 py-2 text-center">
|
| 1183 |
+
<button onclick="removeMenuRow(this)" class="p-1.5 text-cream/30 hover:text-rug-light transition-colors rounded-lg hover:bg-bazaar-800/50">
|
| 1184 |
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
| 1185 |
+
<path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
| 1186 |
</svg>
|
| 1187 |
</button>
|
| 1188 |
</td>
|
|
|
|
| 1217 |
});
|
| 1218 |
const data = await response.json();
|
| 1219 |
if (response.ok && data.success) {
|
| 1220 |
+
alert("✅ منوی جدید با موفقیت ثبت و روی وبسایت همگامسازی شد!");
|
| 1221 |
document.getElementById('menuEditorContainer').classList.add('hidden');
|
| 1222 |
+
fetchCurrentMenu();
|
| 1223 |
+
appendChatMessage('model', '✅ **منوی زنده همگامسازی شد.** سیستم سفارشگیری مشتریان اکنون بر مبنای آخرین لیست ویرایششده کالیبره شده است.');
|
| 1224 |
} else {
|
| 1225 |
alert(data.error || 'بروز خطا در همگامسازی ساختار دادههای جدید.');
|
| 1226 |
}
|
|
|
|
| 1229 |
}
|
| 1230 |
}
|
| 1231 |
|
| 1232 |
+
// ==================== Admin Chat ====================
|
| 1233 |
async function sendAdminMessage(e) {
|
| 1234 |
e.preventDefault();
|
| 1235 |
if (isGenerating) return;
|
|
|
|
| 1257 |
isGenerating = false;
|
| 1258 |
|
| 1259 |
if (response.ok && data.success) {
|
| 1260 |
+
const botBubbleId = appendChatMessage('model', '');
|
| 1261 |
const botBubble = document.getElementById(botBubbleId);
|
| 1262 |
const textResponse = data.response;
|
| 1263 |
let i = 0;
|
|
|
|
| 1264 |
function typeText() {
|
| 1265 |
if (i < textResponse.length && !abortController.signal.aborted) {
|
| 1266 |
botBubble.textContent += textResponse.charAt(i);
|
|
|
|
| 1268 |
document.getElementById('chatMessages').scrollTop = document.getElementById('chatMessages').scrollHeight;
|
| 1269 |
setTimeout(typeText, 6);
|
| 1270 |
} else {
|
| 1271 |
+
botBubble.textContent = '';
|
|
|
|
| 1272 |
botBubble.innerHTML = marked.parse(textResponse);
|
| 1273 |
chatHistory.push({ role: 'model', content: textResponse });
|
|
|
|
| 1274 |
fetchCurrentMenu();
|
| 1275 |
}
|
| 1276 |
}
|
|
|
|
| 1282 |
toggleChatLoading(false);
|
| 1283 |
isGenerating = false;
|
| 1284 |
if (err.name !== 'AbortError') {
|
| 1285 |
+
appendChatMessage('model', '❌ عدم دریافت موفق پاسخ در بستر شبکه: ' + escapeHtml(err.message));
|
| 1286 |
}
|
| 1287 |
}
|
| 1288 |
}
|
|
|
|
| 1312 |
|
| 1313 |
function appendChatMessage(role, content) {
|
| 1314 |
const container = document.getElementById('chatMessages');
|
| 1315 |
+
const bubbleId = 'bubble-admin-' + Date.now() + '-' + Math.random().toString(36).substr(2, 5);
|
| 1316 |
let html = '';
|
| 1317 |
|
| 1318 |
if (role === 'user') {
|
| 1319 |
html = `
|
| 1320 |
<div class="flex justify-end gap-2.5 max-w-[85%] mr-auto">
|
| 1321 |
+
<div class="bg-gradient-to-br from-rug/20 to-rug-dark/30 border border-rug/30 text-cream text-xs rounded-2xl rounded-tl-none px-3.5 py-2.5 leading-relaxed shadow-inner-soft">
|
| 1322 |
${escapeHtml(content)}
|
| 1323 |
</div>
|
| 1324 |
+
<div class="w-8 h-8 rounded-xl bg-gradient-to-br from-ochre/30 to-rug/20 border border-ochre/40 flex items-center justify-center flex-shrink-0">
|
| 1325 |
+
<svg class="w-4 h-4 text-ochre-light" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
| 1326 |
+
<path stroke-linecap="round" stroke-linejoin="round" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
|
| 1327 |
+
</svg>
|
| 1328 |
+
</div>
|
| 1329 |
</div>
|
| 1330 |
`;
|
| 1331 |
} else {
|
|
|
|
|
|
|
| 1332 |
html = `
|
| 1333 |
<div class="flex gap-2.5 max-w-[85%]">
|
| 1334 |
+
<div class="w-8 h-8 rounded-xl bg-gradient-to-br from-turk/20 to-azure-700/20 border border-turk/30 flex items-center justify-center flex-shrink-0">
|
| 1335 |
+
<span class="text-ochre-light font-display text-[11px]">ن</span>
|
| 1336 |
</div>
|
| 1337 |
+
<div id="${bubbleId}" class="bg-bazaar-950/70 border border-ochre/20 text-cream text-xs rounded-2xl rounded-tr-none px-3.5 py-2.5 leading-relaxed shadow-inner-soft chat-markdown">
|
| 1338 |
${content ? marked.parse(content) : ''}
|
| 1339 |
</div>
|
| 1340 |
</div>
|