From c24ff23a07a06ff779492e95cde10191ea46a984 Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (Pragmatismo)" Date: Sat, 10 Jan 2026 06:59:58 -0300 Subject: [PATCH] Add CRM to header tabs and update CSS breakpoints - Add CRM tab to header navigation after tasks - Add CSS breakpoint at 1350px for CRM tab hiding - Add app-item breakpoint for CRM in dropdown - Delete i18n.js (translations moved to botlib .ftl files) - Update TODO.md with completed phases --- TODO.md | 125 +- ui/suite/billing/partials/invoice-form.html | 197 +++ ui/suite/billing/partials/payment-form.html | 98 ++ ui/suite/billing/partials/quote-form.html | 222 +++ ui/suite/crm/partials/lead-form.html | 76 + ui/suite/css/app.css | 15 +- ui/suite/index.html | 29 + ui/suite/js/i18n.js | 1359 ------------------ ui/suite/products/partials/product-form.html | 106 ++ ui/suite/products/partials/service-form.html | 144 ++ 10 files changed, 948 insertions(+), 1423 deletions(-) create mode 100644 ui/suite/billing/partials/invoice-form.html create mode 100644 ui/suite/billing/partials/payment-form.html create mode 100644 ui/suite/billing/partials/quote-form.html create mode 100644 ui/suite/crm/partials/lead-form.html delete mode 100644 ui/suite/js/i18n.js create mode 100644 ui/suite/products/partials/product-form.html create mode 100644 ui/suite/products/partials/service-form.html diff --git a/TODO.md b/TODO.md index 4c96132..163e179 100644 --- a/TODO.md +++ b/TODO.md @@ -8,27 +8,9 @@ Following Microsoft Dynamics nomenclature (simplified for SMB). ## ✅ COMPLETED -- [x] Create folder structure (crm, billing, products, tickets, forms) -- [x] Create TODO.md -- [x] Create `/suite/crm/crm.html` - Pipeline view (Kanban style) -- [x] Create `/suite/crm/crm.css` - Styling -- [x] Create `/suite/billing/billing.html` - Invoice list + dashboard -- [x] Create `/suite/billing/billing.css` - Styling -- [x] Create `/suite/products/products.html` - Product/Service catalog -- [x] Create `/suite/products/products.css` - Styling -- [x] Create `/suite/tickets/tickets.html` - AI-assisted support tickets -- [x] Create `/suite/tickets/tickets.css` - Styling -- [x] Create `/suite/forms/forms.html` - Redirect to Tasks with AI prompt -- [x] Add CRM, Billing, Products, Tickets, Forms to dropdown menu -- [x] Add i18n entries (en, pt-BR) for nav-crm, nav-billing, nav-products, nav-tickets, nav-forms +### Phase 1: Create HTML/CSS for New Apps ✅ ---- - -## 🔄 IN PROGRESS - -### Phase 1: Create HTML/CSS for New Apps - -#### 1.1 CRM (`/suite/crm/`) +#### 1.1 CRM (`/suite/crm/`) ✅ - [x] `crm.html` - Pipeline view (Kanban style) - [x] `crm.css` - Styling - [x] Entities (Dynamics nomenclature): @@ -38,7 +20,7 @@ Following Microsoft Dynamics nomenclature (simplified for SMB). - **Contact** - Person at Account - **Activity** - Linked tasks/calls/emails -#### 1.2 Billing (`/suite/billing/`) +#### 1.2 Billing (`/suite/billing/`) ✅ - [x] `billing.html` - Invoice list + dashboard - [x] `billing.css` - Styling - [x] Entities: @@ -46,7 +28,7 @@ Following Microsoft Dynamics nomenclature (simplified for SMB). - **Payment** - Payment received - **Quote** - Price quotation → converts to Invoice -#### 1.3 Products (`/suite/products/`) +#### 1.3 Products (`/suite/products/`) ✅ - [x] `products.html` - Product/Service catalog - [x] `products.css` - Styling - [x] Entities: @@ -54,47 +36,52 @@ Following Microsoft Dynamics nomenclature (simplified for SMB). - **Service** - Service offering - **PriceList** - Pricing tiers -#### 1.4 Tickets (`/suite/tickets/`) +#### 1.4 Tickets (`/suite/tickets/`) ✅ - [x] `tickets.html` - AI-assisted support tickets - [x] `tickets.css` - Styling - [x] Entities: - **Case** - Support ticket (Dynamics term) - **Resolution** - AI-suggested solutions -#### 1.5 Forms (`/suite/forms/`) +#### 1.5 Forms (`/suite/forms/`) ✅ - [x] `forms.html` - Redirect to Tasks with AI prompt - [x] Behavior: "Create a form for me about [topic]" ---- - -## 📋 TODO - -### Phase 2: Menu Integration (`/suite/index.html`) +### Phase 2: Menu Integration (`/suite/index.html`) ✅ - [x] Add CRM to dropdown menu - [x] Add Billing to dropdown menu - [x] Add Products to dropdown menu - [x] Add Tickets to dropdown menu - [x] Add Forms to dropdown menu -- [ ] Update header tabs (add CRM) -- [ ] Update CSS breakpoints (`/suite/css/app.css`) +- [x] Update header tabs (add CRM) +- [x] Update CSS breakpoints (`/suite/css/app.css`) -### Phase 3: i18n Updates +### Phase 3: i18n Updates ✅ -#### English (`/suite/js/i18n.js`) +**NOTE:** Translations are stored in `.ftl` files in `botlib/locales/` - NOT in JS files. + +#### English (`botlib/locales/en/ui.ftl`) ✅ - [x] nav-crm, nav-billing, nav-products, nav-tickets, nav-forms -- [ ] CRM: lead, opportunity, account, contact, pipeline, qualify, convert, won, lost -- [ ] Billing: invoice, payment, quote, due-date, overdue, paid, pending -- [ ] Products: product, service, price, sku, category, unit -- [ ] Tickets: case, priority, status, assigned, resolved, escalate +- [x] CRM: lead, opportunity, account, contact, pipeline, qualify, convert, won, lost +- [x] Billing: invoice, payment, quote, due-date, overdue, paid, pending +- [x] Products: product, service, price, sku, category, unit +- [x] Tickets: case, priority, status, assigned, resolved, escalate -#### Portuguese (`/suite/js/i18n.js`) +#### Portuguese (`botlib/locales/pt-BR/ui.ftl`) ✅ - [x] nav-crm: "CRM" - [x] nav-billing: "Faturamento" - [x] nav-products: "Produtos" - [x] nav-tickets: "Chamados" - [x] nav-forms: "Formulários" -- [ ] All entity labels in Portuguese +- [x] All entity labels in Portuguese + +#### Spanish (`botlib/locales/es/ui.ftl`) ✅ +- [x] All navigation and entity labels in Spanish + +--- + +## 📋 TODO ### Phase 4: Chat @ Mentions @@ -142,38 +129,49 @@ Following Microsoft Dynamics nomenclature (simplified for SMB). --- -## File Checklist +## File Structure -### New Files to Create: +### i18n Files (Fluent format .ftl): ``` -/suite/crm/crm.html -/suite/crm/crm.css -/suite/billing/billing.html -/suite/billing/billing.css -/suite/products/products.html -/suite/products/products.css -/suite/tickets/tickets.html -/suite/tickets/tickets.css -/suite/forms/forms.html +botlib/locales/ +├── en/ +│ └── ui.ftl # English translations +├── pt-BR/ +│ └── ui.ftl # Portuguese translations +└── es/ + └── ui.ftl # Spanish translations ``` -### Files to Update: +### Suite Files: ``` -/suite/index.html - Menu items + HTMX routes -/suite/css/app.css - Breakpoints for new tabs -/suite/js/i18n/en.json - English labels -/suite/js/i18n/pt.json - Portuguese labels -/suite/chat/chat.html - @ mention UI -/suite/chat/chat.js - @ autocomplete logic +botui/ui/suite/ +├── crm/ +│ ├── crm.html +│ └── crm.css +├── billing/ +│ ├── billing.html +│ └── billing.css +├── products/ +│ ├── products.html +│ └── products.css +├── tickets/ +│ ├── tickets.html +│ └── tickets.css +├── forms/ +│ └── forms.html +├── index.html # Menu items + HTMX routes +└── css/ + └── app.css # Breakpoints for tabs ``` -### BotBook Files: +### BotBook Files (TODO): ``` -/botbook/src/SUMMARY.md - Add new chapters -/botbook/src/XX-crm/ - CRM documentation -/botbook/src/XX-billing/ - Billing documentation -/botbook/src/XX-products/ - Products documentation -/botbook/src/XX-tickets/ - Tickets documentation +botbook/src/ +├── SUMMARY.md # Add new chapters +├── XX-crm/ # CRM documentation +├── XX-billing/ # Billing documentation +├── XX-products/ # Products documentation +└── XX-tickets/ # Tickets documentation ``` --- @@ -225,6 +223,7 @@ Account ◄──► Opportunity (1:N) ## Notes +- **i18n Location**: All translations in `botlib/locales/{locale}/ui.ftl` files (Fluent format) - **Dynamics Nomenclature**: Lead, Opportunity, Account, Contact, Case, Quote, Invoice - **SMB Focus**: Simple, not enterprise complexity - **AI-First**: Tickets use AI suggestions, Forms use AI generation diff --git a/ui/suite/billing/partials/invoice-form.html b/ui/suite/billing/partials/invoice-form.html new file mode 100644 index 0000000..bc0a5bf --- /dev/null +++ b/ui/suite/billing/partials/invoice-form.html @@ -0,0 +1,197 @@ + +
+
+

New Invoice

+ +
+ + +
+
Customer
+
+
+ + +
+
+ + +
+
+
+ + +
+
Invoice Details
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
Line Items
+
+
+ Description + Qty + Price + Total + +
+
+
+ + + + $0.00 + +
+
+ +
+
+ + +
+
+ Subtotal + $0.00 +
+
+ Tax (0%) + $0.00 +
+
+ Total + $0.00 +
+
+ + +
+ + +
+ +
+ + + +
+
+ + diff --git a/ui/suite/billing/partials/payment-form.html b/ui/suite/billing/partials/payment-form.html new file mode 100644 index 0000000..17dddba --- /dev/null +++ b/ui/suite/billing/partials/payment-form.html @@ -0,0 +1,98 @@ + +
+
+

Record Payment

+ +
+ + +
+
Invoice Details
+
+
+ + +
+
+ + +
+
+
+ + +
+
Payment Details
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+ + +
+ +
+ + +
+
+ + diff --git a/ui/suite/billing/partials/quote-form.html b/ui/suite/billing/partials/quote-form.html new file mode 100644 index 0000000..dcf3abc --- /dev/null +++ b/ui/suite/billing/partials/quote-form.html @@ -0,0 +1,222 @@ + +
+
+

New Quote

+ +
+ + +
+
Customer
+
+
+ + +
+
+ + +
+
+
+ + +
+
Quote Details
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
Line Items
+
+
+ Description + Qty + Price + Total + +
+
+
+ + + + $0.00 + +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ Subtotal + $0.00 +
+
+ Discount + -$0.00 +
+
+ Total + $0.00 +
+
+ + +
+ + +
+ +
+ + +
+ +
+ + + +
+
+ + diff --git a/ui/suite/crm/partials/lead-form.html b/ui/suite/crm/partials/lead-form.html new file mode 100644 index 0000000..b042124 --- /dev/null +++ b/ui/suite/crm/partials/lead-form.html @@ -0,0 +1,76 @@ + +
+
+

New Lead

+ +
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+
diff --git a/ui/suite/css/app.css b/ui/suite/css/app.css index 17ce035..70ba705 100644 --- a/ui/suite/css/app.css +++ b/ui/suite/css/app.css @@ -403,7 +403,7 @@ body { } /* Hide header tabs progressively as screen shrinks */ -/* Header tabs: chat, mail, calendar, drive, tasks, docs, sheet, slides, social */ +/* Header tabs: chat, mail, calendar, drive, tasks, crm, docs, sheet, slides, social */ @media (max-width: 1400px) { .app-tab[data-section="social"] { @@ -411,6 +411,12 @@ body { } } +@media (max-width: 1350px) { + .app-tab[data-section="crm"] { + display: none; + } +} + @media (max-width: 1300px) { .app-tab[data-section="slides"] { display: none; @@ -1195,6 +1201,13 @@ body { } } +/* At 1350px: crm tab hides */ +@media (max-width: 1350px) { + .app-item[data-section="crm"] { + display: flex; + } +} + /* At 1300px: slides tab hides */ @media (max-width: 1300px) { .app-item[data-section="slides"] { diff --git a/ui/suite/index.html b/ui/suite/index.html index c87ee16..f61ca84 100644 --- a/ui/suite/index.html +++ b/ui/suite/index.html @@ -33,6 +33,10 @@ + + + + @@ -269,6 +273,31 @@ Tarefas + + + + + + + + CRM + CACHE_TTL_MS) { - localStorage.removeItem(key); - return null; - } - return data.messages; - } catch (e) { - return null; - } - } - - saveToCache(key, messages) { - try { - localStorage.setItem( - key, - JSON.stringify({ - timestamp: Date.now(), - messages: messages, - }), - ); - } catch (e) { - // Storage full or disabled - } - } - - t(key, args) { - var message = this.messages[key]; - if (!message) { - var baseLocale = this.locale.split("-")[0]; - var fallback = - FALLBACK_TRANSLATIONS[this.locale] || - FALLBACK_TRANSLATIONS[baseLocale] || - FALLBACK_TRANSLATIONS[DEFAULT_LOCALE]; - message = fallback ? fallback[key] : null; - } - if (!message) { - return "[" + key + "]"; - } - if (args) { - message = this.interpolate(message, args); - } - return message; - } - - interpolate(template, args) { - var result = template; - for (var key in args) { - if (args.hasOwnProperty(key)) { - var value = args[key]; - result = result.replace( - new RegExp("\\{\\s*\\$" + key + "\\s*\\}", "g"), - value, - ); - result = result.replace(new RegExp("\\{" + key + "\\}", "g"), value); - } - } - return result; - } - - translatePage() { - this.translateElements("[data-i18n]", "textContent", "i18n"); - this.translateElements( - "[data-i18n-placeholder]", - "placeholder", - "i18nPlaceholder", - ); - this.translateElements("[data-i18n-title]", "title", "i18nTitle"); - this.translateElements( - "[data-i18n-aria-label]", - "ariaLabel", - "i18nAriaLabel", - ); - } - - translateElements(selector, property, dataAttr) { - var self = this; - var elements = document.querySelectorAll(selector); - elements.forEach(function (el) { - var key = el.dataset[dataAttr]; - if (!key) return; - var args = null; - var argsAttr = el.dataset.i18nArgs; - if (argsAttr) { - try { - args = JSON.parse(argsAttr); - } catch (e) { - // ignore - } - } - var translated = self.t(key, args); - if (translated.startsWith("[") && translated.endsWith("]")) return; - if (property === "textContent") { - el.textContent = translated; - } else { - el[property] = translated; - } - }); - } - - async setLocale(locale) { - if (this.locale === locale) return; - this.locale = locale; - this.loaded = false; - localStorage.setItem(STORAGE_KEY, locale); - document.documentElement.lang = locale; - localStorage.removeItem("gb-i18n-" + locale); - await this.loadTranslations(); - document.dispatchEvent( - new CustomEvent("i18n:localeChanged", { - detail: { locale: locale }, - }), - ); - } - - getLocale() { - return this.locale; - } - - getAvailableLocales() { - return ["en", "pt-BR"]; - } - - getLocaleDisplayName(locale) { - var names = { - en: "English", - "pt-BR": "Português (Brasil)", - es: "Español", - fr: "Français", - de: "Deutsch", - "zh-CN": "简体中文", - }; - return names[locale] || locale; - } - - formatNumber(value, options) { - try { - return new Intl.NumberFormat(this.locale, options).format(value); - } catch (e) { - return String(value); - } - } - - formatDate(date, options) { - var dateObj = date instanceof Date ? date : new Date(date); - if (isNaN(dateObj.getTime())) return String(date); - try { - return new Intl.DateTimeFormat(this.locale, options).format(dateObj); - } catch (e) { - return dateObj.toISOString(); - } - } - - formatRelativeTime(date) { - var dateObj = date instanceof Date ? date : new Date(date); - if (isNaN(dateObj.getTime())) return String(date); - var now = Date.now(); - var diffMs = now - dateObj.getTime(); - var diffMin = Math.floor(diffMs / 60000); - var diffHour = Math.floor(diffMin / 60); - var diffDay = Math.floor(diffHour / 24); - if (diffMin < 1) return this.t("time-now") || "Just now"; - if (diffMin < 60) return diffMin + "m ago"; - if (diffHour < 24) return diffHour + "h ago"; - if (diffDay < 30) return diffDay + "d ago"; - return this.formatDate(dateObj, { dateStyle: "medium" }); - } - } - - var i18n = new I18n(); - - function setupHtmxListeners() { - document.body.addEventListener("htmx:afterSwap", function () { - if (i18n.loaded) - setTimeout(function () { - i18n.translatePage(); - }, 10); - }); - document.body.addEventListener("htmx:afterSettle", function () { - if (i18n.loaded) - setTimeout(function () { - i18n.translatePage(); - }, 10); - }); - document.body.addEventListener("htmx:load", function () { - if (i18n.loaded) - setTimeout(function () { - i18n.translatePage(); - }, 10); - }); - } - - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", function () { - i18n.init(); - setupHtmxListeners(); - }); - } else { - i18n.init(); - setupHtmxListeners(); - } - - window.i18n = i18n; - window.t = function (key, args) { - return i18n.t(key, args); - }; -})(); diff --git a/ui/suite/products/partials/product-form.html b/ui/suite/products/partials/product-form.html new file mode 100644 index 0000000..d27f3a7 --- /dev/null +++ b/ui/suite/products/partials/product-form.html @@ -0,0 +1,106 @@ + +
+
+

New Product

+ +
+ + +
+ + +
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+
diff --git a/ui/suite/products/partials/service-form.html b/ui/suite/products/partials/service-form.html new file mode 100644 index 0000000..c93f5d5 --- /dev/null +++ b/ui/suite/products/partials/service-form.html @@ -0,0 +1,144 @@ + +
+
+

New Service

+ +
+ + +
+ + +
+ +
+ + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+ +