## 📝 Documentation Update Added comprehensive progress note for notification system work completed on November 11, 2025. ### Documented: **Phase 1: UI/UX Refinements** - Channels page simplification - Events page density reduction - Visual improvements **Phase 2: Critical Bug Fixes** - Toggle not saving (get_json_params fix) - Multiple API calls (optimistic update removal) - Wrong event defaults (data structure fix) - Events cannot be enabled (path fix) **Phase 3: Push URL Strategy** - Dynamic URLs recommendation - Event-specific deep linking - Template variables support - Implementation plan ### Testing Results: - All toggles working correctly - State persistence verified - Network optimization confirmed ### Next Steps: - Dynamic push notification URLs - Per-event URL configuration - Rich notification content --- **Status:** ✅ All issues resolved and documented
84 KiB
WooNooW Project Progress Note
Last Updated: November 11, 2025, 4:10 PM (GMT+7)
Overview
WooNooW is a hybrid WordPress + React SPA replacement for WooCommerce Admin. It focuses on performance, UX consistency, and extensibility with SSR-safe endpoints and REST-first design. The plugin integrates deeply with WooCommerce’s data store (HPOS ready) and provides a modern React-based dashboard and order management system.
Current Structure
woonoow/
├── admin-spa/
│ ├── src/
│ │ ├── components/
│ │ │ ├── filters/
│ │ │ │ ├── DateRange.tsx
│ │ │ │ └── OrderBy.tsx
│ │ │ └── CommandPalette.tsx
│ │ ├── hooks/
│ │ │ └── useShortcuts.tsx
│ │ ├── lib/
│ │ │ ├── api.ts
│ │ │ ├── currency.ts
│ │ │ ├── dates.ts
│ │ │ ├── query-params.ts
│ │ │ ├── useCommandStore.ts
│ │ │ └── utils.ts
│ │ ├── pages/
│ │ │ └── orders/
│ │ │ ├── partials
│ │ │ │ └── OrderForm.tsx
│ │ │ ├── Orders.tsx
│ │ │ ├── OrdersNew.tsx
│ │ │ └── OrderShow.tsx
│ │ ├── routes/
│ │ │ └── Dashboard.tsx
│ │ ├── types/
│ │ │ └── qrcode.d.ts
│ │ ├── App.tsx
│ │ ├── index.css
│ │ └── main.tsx
│ └── vite.config.ts
├── includes/
│ ├── Admin/
│ │ ├── Assets.php
│ │ └── Menu.php
│ ├── Api/
│ │ ├── CheckoutController.php
│ │ ├── OrdersController.php
│ │ ├── Permissions.php
│ │ └── Routes.php
│ ├── Compat/
│ │ ├── HideWooMenus.php
│ │ └── HooksShim.php
│ └── Core/
│ ├── DataStores/
│ │ ├── OrderStore_HPOS.php
│ │ └── OrderStore.php
│ ├── Mail/
│ │ ├── MailQueue.php
│ │ └── WooEmailOverride.php
│ ├── Bootstrap.php
│ └── Features.php
├── woonoow.php
└── docs (project notes, SOP, etc.)
Major Implementations
✅ Backend (PHP)
- Routes.php registers
/orders,/checkout,/countries,/payments,/shippingsendpoints. - OrdersController.php
index()– Paginated, filterable orders list with date range, orderby, order.show()– Detailed order view with items, billing, totals, and formatted addresses.create()– Create order via admin (supports multi-item, no customer registration required).countries(),payments(),shippings()added for dynamic form data.- Permissions handled via helper
permission_callback_admin().
- CheckoutController.php – Placeholder for frontend (anonymous) checkout path.
- Assets.php – Injects localized nonce & REST base URL for SPA.
✅ Frontend (React)
- SPA with sticky sidebar and topbar.
- CommandPalette + keyboard shortcuts (D, R, O).
- Orders module fully functional:
Orders.tsx: Paginated list, filters (status/date/orderby), and SPA navigation.OrderShow.tsx: Detailed view with print, label mode (QR/barcode ready), responsive layout.OrdersNew.tsx: Full order creation form (billing/shipping, items, payments, shippings, coupons).
- Filters (
DateRange,OrderBy) use ShadcnSelect. - Sticky nav and fullscreen mode implemented.
- Responsive and print-optimized layouts (with dedicated CSS rules in
index.css).
🧠 Data Flow
- All requests handled via
OrdersApi(inlib/api.ts) using unifiedapi.get/postwrapper with nonce. - React Query handles caching, pagination, and mutations.
- URL query sync via
query-params.tsfor persistent filters.
🧩 UI / UX
- Shadcn UI components standardized across input/select.
- Print and label modes render order as invoice/shipping label.
- Label includes QR (via
qrcodenpm package) — tracking or order ID encoded.
Known Issues / TODO
- Fullscreen scroll issue – body scroll locked improperly; needs fix in layout wrapper.
- Select z-index in fullscreen – dropdowns render under content; requires portal layering fix.
- State/Province handling – add conditional Select for country states.
- Invoice / Label layout – needs standalone print-friendly component (A5/A6 sizing option).
- Permission helper expansion – support
editorandshop_managerroles. - Coupons logic – confirm multiple coupon support (WooCommerce natively supports multiple coupons per order).
- Upcoming: Dashboard metrics (revenue, orders, customers) via new
/statsendpoint.
Notes for Next Session
- Start by finishing OrdersNew.tsx responsive layout & totals preview card.
- Add states endpoint in
OrdersController. - Move Shadcn dropdowns to portals for fullscreen mode.
- Prepare print-friendly components (
InvoicePrint,LabelPrint). - Ensure
index.cssglobal variables handle light/dark theme for WooNooW.
Last synced: 2025‑10‑25 20:00 GMT+7
Next milestone: Order creation polish + Dashboard overview.
🔄 Recent Progress — October 27, 2025
🧩 Core Architecture
- Added dynamic WooCommerce menu collector in
includes/Admin/Menu.php:- Scrapes
$menuand$submenuafter all plugins register menus. - Filters Woo-related slugs and localizes
WNM_WC_MENUSto SPA. - Guarantees future add-ons automatically appear in the SPA nav.
- Scrapes
- Updated
Assets.phphandle targeting to ensure localization attaches to correct dev/prod handles.
🧭 Frontend Navigation
- Dynamic Quick Nav implemented in
App.tsx:- Reads
window.WNM_WC_MENUS.items(provided by backend collector). - Renders scrollable top navigation bar.
- Maps known WooCommerce admin URLs → SPA routes.
- Falls back to legacy bridge for unmapped items.
- Reads
- Added hovercard filters for mobile in
Orders.tsx. - Integrated Shadcn components and unified styling for consistent look across SPA.
📚 Documentation & Planning
- Created SPA_ADMIN_MENU_PLAN.md — authoritative mapping of WooCommerce default menus to SPA routes.
- Includes regex route map for legacy → SPA translation.
- Added visual menu tree (default WooCommerce sidebar hierarchy).
- Defined Proposed SPA Main Menu (Authoritative) structure:
- Dashboard → all analytics/reports (merged with Marketing)
- Orders → CRUD
- Products → CRUD
- Coupons → CRUD
- Customers → derived from orders/users
- Settings → all Woo tabs + Status + Extensions
- Clarified that “Marketing / Hub” is part of WooCommerce Admin (extension recommendations) and will be folded into Dashboard metrics.
🧱 Next Steps
- Update SPA quick-nav to render based on Proposed SPA Main Menu, not
wp-adminstructure. - Extend
/lib/routesorApp.tsxto handle/dashboard/*routes for reports. - Implement
/dashboardoverview and/customerslist (buyer‑only dataset). - Add settings router structure for tabbed
/settings/*pages. - Migrate Status + Extensions into Settings as planned.
- Maintain compatibility for add-on menus using
WNM_WC_MENUSdynamic injection.
Last synced: 2025‑10‑28 06:06 GMT+7 Next milestone: Enhance order management features and implement advanced filtering.
🗑️ Bulk Delete Operations — October 28, 2025 (Morning)
✅ Complete Bulk Delete Feature Implemented
- Frontend (Orders/index.tsx): Multi-select checkboxes with "Select All" functionality
- Backend (OrdersController.php): DELETE endpoint with HPOS support
- Confirmation Dialog: Shadcn Dialog with clear warning and action buttons
- Smart Deletion: Parallel deletion with graceful error handling
- User Feedback: Toast notifications for success/partial/failure states
- Logging: WooCommerce logger integration for audit trail
🎯 Features
- Checkbox column as first column in orders table
- Delete button appears when items selected (shows count)
- Confirmation dialog prevents accidental deletion
- HPOS-aware deletion (soft delete to trash)
- Handles both HPOS and legacy post-based orders
- Parallel API calls with
Promise.allSettled - Automatic list refresh after deletion
- Full i18n support for all UI strings
🌍 Internationalization (i18n) — October 28, 2025 (Morning)
✅ Complete Translation Coverage Implemented
- Frontend (18 files): All user-facing strings in Orders, Dashboard, Coupons, Customers, Settings, Navigation, Filters, and Command Palette now use
__()wrapper - Backend (5 files): All error messages in API controllers translated using WordPress
__()function - Documentation: Created comprehensive
I18N_IMPLEMENTATION_GUIDE.mdand updatedPROJECT_SOP.mdwith sprintf examples - Total strings translated: ~330+ strings across 27 files
- Pattern established: Consistent use of
@/lib/i18nwrapper for frontend,__('string', 'woonoow')for backend - Ready for: POT file generation and multilingual deployment
📝 Translation Infrastructure
- Custom
i18n.tswrapper leveraging WordPresswp.i18nfor frontend consistency - Centralized error handling with translatable messages in
errorHandling.ts - All validation messages, UI labels, navigation items, and error states fully translatable
- Both simple strings and sprintf-formatted strings supported
🔧 Recent Fixes — October 27, 2025 (Evening)
🧩 Navigation / Menu Synchronization
- Hardened
tree.tsas immutable single source of truth (deep frozen) for SPA menus. - Verified
orders.children= [] so Orders has no submenu; ensured SubmenuBar reads strictly from props. - Replaced
SubmenuBar.tsxwith minimal version rendering only prop items — no fallbacks. - Cleaned Orders route files (
index.tsx,New.tsx,Edit.tsx,Detail.tsx) to remove local static tabs (“Orders / New Order”). - Confirmed final architecture: tree.ts → useActiveSection → SubmenuBar pipeline.
- Added runtime debug logs (
[SubmenuMount]) inApp.tsxto trace submenu rendering. - Discovered root cause: legacy SPA bundle still enqueued; restored and verified
Assets.phpto ensure only one SPA entry script runs.
📱 Responsive / Mobile
- Added global
tokens.cssfor interactive element sizing (.ui-ctrlh-11 md:h-9) improving mobile UX. - Applied global sizing to input, select, and button components via
index.cssand tokens import.
🧭 Layout & Fullscreen
- Fixed duplicate scrollbars in fullscreen mode by adjusting container overflow.
- Sidebar limited to desktop + fullscreen; mobile uses topbar version for consistency.
⚙️ System Checks
- Updated
Assets.phpto exposewindow.wnwglobal withisDev,devServer, andadminUrlfor SPA environment bridging. - Updated
useActiveSection.tsto rely solely onwindow.wnw.isDev.
✅ Next Steps
- Verify only one SPA script enqueued (
woonoow-admin-spa); remove legacy duplicates. - Confirm menu tree auto-sync with Woo add-ons (via
MenuProvider). - Add
/dashboardand/customersroutes with consistent layout + submenu. - Standardize toast notifications across modules using Sonner.
- Prepare print-friendly
InvoicePrintandLabelPrintcomponents for order detail.
Last synced: 2025‑10‑27 23:59 GMT+7 Next milestone: Dashboard overview + unified Settings SPA.
🔌 Addon Injection System — October 28, 2025 (Complete)
✅ PRODUCTION READY - Full Implementation Complete
Status: 10/10 Readiness Score
Implementation Time: 2-3 days
Total Changes: 15 files, ~3050 net lines
🎯 What Was Built
Backend (PHP) - 100% Complete
-
AddonRegistry.php (200+ lines)
- Central addon metadata registry
- Dependency validation (WooCommerce, WordPress, plugins)
- Version checking and enable/disable control
- Filter:
woonoow/addon_registry
-
RouteRegistry.php (170+ lines)
- SPA route registration for addons
- Capability-based filtering
- Route validation and sanitization
- Filter:
woonoow/spa_routes
-
NavigationRegistry.php (180+ lines)
- Dynamic navigation tree building
- Main menu injection
- Per-section submenu injection
- Filters:
woonoow/nav_tree,woonoow/nav_tree/{key}/children
-
Bootstrap.php - Integrated all registries
-
Assets.php - Exposed data to frontend via window globals
Frontend (React/TypeScript) - 100% Complete
- nav/tree.ts - Dynamic navigation tree (reads from
window.WNW_NAV_TREE) - hooks/useActiveSection.ts - Dynamic path matching
- App.tsx - AddonRoute component with lazy loading, error handling, loading states
- Removed Bridge/iframe - Cleaned ~150 lines of legacy code
Documentation - 100% Complete
-
ADDON_INJECTION_GUIDE.md (900+ lines)
- Quick start (5-minute integration)
- Complete API reference
- Component development guide
- Best practices and examples
-
HOOKS_REGISTRY.md (500+ lines)
- Complete hook tree structure
- All active hooks documented
- Priority guidelines
- Usage examples
-
PROJECT_SOP.md - Section 6 added (320+ lines)
- Hook naming convention
- Filter template pattern
- Non-React addon development (3 approaches)
- Development checklist
-
IMPLEMENTATION_SUMMARY.md (400+ lines)
- Complete implementation summary
- Questions answered
- Quick reference guide
🚀 Key Features
For Addon Developers:
- ✅ 5-minute integration with simple filters
- ✅ Three development approaches:
- Traditional PHP - No React, uses WordPress admin pages
- Vanilla JS - SPA integration without React
- React - Full SPA with React (optional)
- ✅ Zero configuration - automatic discovery
- ✅ Dependency validation
- ✅ Error handling and loading states
- ✅ Full i18n support
For End Users:
- ✅ Seamless integration (no iframes!)
- ✅ Fast loading with lazy loading
- ✅ Consistent UI
- ✅ Mobile responsive
📚 Hook Structure
woonoow/
├── addon_registry ✅ ACTIVE (Priority: 20)
├── spa_routes ✅ ACTIVE (Priority: 25)
├── nav_tree ✅ ACTIVE (Priority: 30)
│ └── {section_key}/children ✅ ACTIVE (Priority: 30)
├── dashboard/widgets 📋 PLANNED
├── order/detail/panels 📋 PLANNED
└── admin_is_dev ✅ ACTIVE
🎓 Orders Module as Reference
Orders module serves as the model for all future implementations:
- Clean route structure (
/orders,/orders/new,/orders/:id) - No submenu (by design)
- Full CRUD operations
- Type-safe components
- Proper error handling
- Mobile responsive
- i18n complete
📦 Example Addon Created
Location: examples/example-addon.php
- Complete working example
- Addon registration
- Route registration
- Navigation injection
- REST API endpoint
- React component
✅ Success Criteria - ALL MET
- Remove Bridge/iframe system
- Implement AddonRegistry
- Implement RouteRegistry
- Implement NavigationRegistry
- Dynamic route loading
- Dynamic navigation
- Component lazy loading
- Error handling
- Comprehensive documentation
- Hook registry with tree structure
- Non-React support documented
- Production ready
🎯 What This Enables
Addons can now:
- Register with metadata & dependencies
- Add custom SPA routes
- Inject main menu items
- Inject submenu items
- Load React components dynamically
- Use vanilla JavaScript (no React)
- Use traditional PHP/HTML/CSS
- Access WooNooW APIs
- Declare capabilities
- Handle errors gracefully
Use Cases:
- WooCommerce Subscriptions
- Bookings & Appointments
- Memberships
- Custom Reports
- Third-party integrations
- Custom product types
- Marketing automation
- ANY custom functionality!
📝 Documentation Files
ADDON_INJECTION_GUIDE.md- Developer guide (900+ lines)HOOKS_REGISTRY.md- Hook reference (500+ lines)PROJECT_SOP.md- Section 6 (320+ lines)IMPLEMENTATION_SUMMARY.md- Summary (400+ lines)ADDONS_ADMIN_UI_REQUIREMENTS.md- Updated statusADDON_INJECTION_READINESS_REPORT.md- Technical analysisexamples/example-addon.php- Working example
Total Documentation: 2400+ lines
Last synced: 2025‑10‑28 09:35 GMT+7
Next milestone: Test addon system, then proceed with Dashboard module development.
💳 Payment Gateway Integration — October 28, 2025 (Afternoon)
✅ Phase 1: Core Integration - COMPLETE
Problem: Payment gateways (Tripay, Duitku, Xendit) not receiving transactions when orders created via WooNooW Admin.
Root Cause: WooCommerce payment gateways expect process_payment() to be called during checkout, but admin-created orders bypass this flow.
🎯 Solution Implemented
Backend Changes
File: includes/Api/OrdersController.php
-
Auto-trigger payment processing (lines 913-915)
if ( $payment_method && $status === 'pending' ) { self::process_payment_gateway( $order, $payment_method ); } -
New method:
process_payment_gateway()(lines 1509-1602)- Initializes WooCommerce cart (prevents
empty_cart()errors) - Initializes WooCommerce session (prevents
set()errors) - Gets payment gateway instance
- Handles channel-based IDs (e.g.,
tripay_bniva,bacs_account_0) - Calls
$gateway->process_payment($order_id) - Stores result metadata (
_woonoow_payment_redirect) - Adds order notes
- Logs success/failure
- Graceful error handling (doesn't fail order creation)
- Initializes WooCommerce cart (prevents
🔧 Technical Details
Session Initialization:
// Initialize cart
if ( ! WC()->cart ) {
WC()->initialize_cart();
}
// Initialize session
if ( ! WC()->session || ! WC()->session instanceof \WC_Session ) {
WC()->initialize_session();
}
Why needed:
- Payment gateways call
WC()->cart->empty_cart()after successful payment - Payment gateways call
WC()->session->set()to store payment data - Admin-created orders don't have active cart/session
- Without initialization:
Call to a member function on nullerrors
📊 Features
- ✅ Universal solution (works with all WooCommerce payment gateways)
- ✅ Handles Tripay, Duitku, Xendit, PayPal, and custom gateways
- ✅ Stores payment metadata for display
- ✅ Adds order notes for audit trail
- ✅ Error logging for debugging
- ✅ Non-blocking (order creation succeeds even if payment fails)
- ✅ Channel support (e.g., Tripay BNI VA, Mandiri VA, etc.)
📚 Documentation
PAYMENT_GATEWAY_INTEGRATION.md- Complete implementation guidePAYMENT_GATEWAY_PATTERNS.md- Analysis of 4 major gateways
💰 Order Totals Calculation — October 28, 2025 (Afternoon)
✅ COMPLETE - Shipping & Coupon Fixes
Problem: Orders created via WooNooW showed incorrect totals:
- Shipping cost always Rp0 (hardcoded)
- Coupon discounts not calculated
- Total = products only (missing shipping)
🎯 Solutions Implemented
1. Shipping Cost Calculation
File: includes/Api/OrdersController.php (lines 830-858)
Before:
$ship_item->set_total( 0 ); // ❌ Always 0
After:
// Get shipping method cost from settings
$shipping_cost = 0;
if ( $instance_id ) {
$zones = \WC_Shipping_Zones::get_zones();
foreach ( $zones as $zone ) {
foreach ( $zone['shipping_methods'] as $method ) {
if ( $method->id === $method_id && $method->instance_id == $instance_id ) {
$shipping_cost = $method->get_option( 'cost', 0 ); // ✅ Actual cost!
break 2;
}
}
}
}
$ship_item->set_total( $shipping_cost );
2. Coupon Discount Calculation
File: includes/Api/OrdersController.php (lines 876-886)
Before:
$citem = new \WC_Order_Item_Coupon();
$citem->set_code( $coupon->get_code() );
$order->add_item( $citem ); // ❌ No discount calculated
After:
$order->apply_coupon( $coupon ); // ✅ Calculates discount!
📊 Results
Order Calculation Flow:
1. Add products → Subtotal: Rp112.000
2. Add shipping → Get cost from settings → Rp25.000 ✅
3. Apply coupons → Calculate discount → -RpX ✅
4. Calculate totals → Products + Shipping - Discount + Tax ✅
5. Payment gateway → Receives correct total ✅
Example:
- Products: Rp112.000
- Shipping: Rp25.000
- Total: Rp137.000 ✅ (was Rp112.000 ❌)
📚 Documentation
ORDER_TOTALS_FIX.md- Complete fix documentation with test cases
🎨 Order Totals Display — October 28, 2025 (Afternoon)
✅ COMPLETE - Frontend Breakdown Display
Problem: Order form only showed items count and subtotal. No shipping, discount, or total visible.
🎯 Solution Implemented
Backend: Add Shipping Cost to API
File: includes/Api/OrdersController.php (lines 1149-1159)
Added cost field to shipping methods API:
$rows[] = [
'id' => $instance ? "{$id}:{$instance}" : $id,
'method' => $id,
'title' => (string) ( $m->title ?? $m->get_method_title() ?? $id ),
'cost' => (float) $cost, // ✅ New!
];
Frontend: Complete Order Breakdown
File: admin-spa/src/routes/Orders/partials/OrderForm.tsx
Added calculations:
// Calculate shipping cost
const shippingCost = React.useMemo(() => {
if (!shippingMethod) return 0;
const method = shippings.find(s => s.id === shippingMethod);
return method ? Number(method.cost) || 0 : 0;
}, [shippingMethod, shippings]);
// Calculate order total
const orderTotal = React.useMemo(() => {
return itemsTotal + shippingCost;
}, [itemsTotal, shippingCost]);
Display:
<div className="rounded-md border px-3 py-2 text-sm bg-white/60 space-y-1.5">
<div>Items: 2</div>
<div>Subtotal: Rp112.000</div>
<div>Shipping: Rp25.000</div>
<div>Discount: (calculated on save)</div>
<div className="border-t pt-1.5 font-medium">
Total (est.): Rp137.000
</div>
</div>
📊 Features
- ✅ Real-time shipping cost display
- ✅ Subtotal + Shipping = Total
- ✅ Discount note (calculated server-side)
- ✅ Professional breakdown layout
- ✅ Responsive design
💡 Coupon Calculation - Best Practice
Decision: Show "(calculated on save)" instead of frontend calculation
Why:
- ✅ Accurate - Backend has all coupon rules
- ✅ Secure - Can't bypass restrictions
- ✅ Simple - No complex frontend logic
- ✅ Reliable - Always matches final total
Industry Standard: Shopify, WooCommerce, Amazon all calculate discounts server-side.
📚 Documentation
ORDER_TOTALS_DISPLAY.md- Complete display documentation
🎫 Phase 2: Payment Display — October 28, 2025 (Evening)
✅ COMPLETE - Payment Instructions Card
Goal: Display payment gateway metadata (VA numbers, QR codes, expiry, etc.) in Order Detail view.
🎯 Solution Implemented
Backend: Payment Metadata API
File: includes/Api/OrdersController.php
New method: get_payment_metadata() (lines 1604-1662)
private static function get_payment_metadata( $order ): array {
$meta_keys = apply_filters( 'woonoow/payment_meta_keys', [
// Tripay
'_tripay_payment_pay_code' => __( 'Payment Code', 'woonoow' ),
'_tripay_payment_reference' => __( 'Reference', 'woonoow' ),
'_tripay_payment_expired_time' => __( 'Expires At', 'woonoow' ),
'_tripay_payment_amount' => __( 'Amount', 'woonoow' ),
// Duitku
'_duitku_va_number' => __( 'VA Number', 'woonoow' ),
'_duitku_payment_url' => __( 'Payment URL', 'woonoow' ),
// Xendit
'_xendit_invoice_id' => __( 'Invoice ID', 'woonoow' ),
'_xendit_invoice_url' => __( 'Invoice URL', 'woonoow' ),
// WooNooW
'_woonoow_payment_redirect' => __( 'Payment URL', 'woonoow' ),
], $order );
// Extract, format timestamps, amounts, booleans
// Return structured array
}
Features:
- ✅ Auto-formats timestamps → readable dates
- ✅ Auto-formats amounts → currency (Rp70.000)
- ✅ Auto-formats booleans → Yes/No
- ✅ Extensible via
woonoow/payment_meta_keysfilter - ✅ Supports Tripay, Duitku, Xendit, custom gateways
Added to order API response (line 442):
'payment_meta' => self::get_payment_metadata($order),
Frontend: Payment Instructions Card
File: admin-spa/src/routes/Orders/Detail.tsx (lines 212-242)
{order.payment_meta && order.payment_meta.length > 0 && (
<div className="rounded border overflow-hidden">
<div className="px-4 py-3 border-b font-medium flex items-center gap-2">
<Ticket className="w-4 h-4" />
{__('Payment Instructions')}
</div>
<div className="p-4 space-y-3">
{order.payment_meta.map((meta: any) => (
<div key={meta.key} className="grid grid-cols-[120px_1fr] gap-2 text-sm">
<div className="opacity-60">{meta.label}</div>
<div className="font-medium">
{meta.key.includes('url') ? (
<a href={meta.value} target="_blank" className="text-blue-600">
{meta.value} <ExternalLink className="w-3 h-3" />
</a>
) : meta.key.includes('amount') ? (
<span dangerouslySetInnerHTML={{ __html: meta.value }} />
) : (
meta.value
)}
</div>
</div>
))}
</div>
</div>
)}
📊 Example Display
Tripay BNI VA Order:
┌─────────────────────────────────────────┐
│ 🎫 Payment Instructions │
├─────────────────────────────────────────┤
│ Payment Code 8808123456789012 │
│ Reference T1234567890 │
│ Expires At Oct 28, 2025 11:59 PM │
│ Amount Rp137.000 │ ← Currency formatted!
│ Payment Type BNIVA │
│ Payment URL https://tripay.co/... 🔗│
└─────────────────────────────────────────┘
📊 Features
- ✅ Only shows if payment metadata exists
- ✅ Ticket icon for visual clarity
- ✅ Grid layout: Label | Value
- ✅ Auto-detects URLs → clickable with external icon
- ✅ Currency formatting for amounts
- ✅ Timestamp formatting
- ✅ Responsive design
- ✅ Extensible for custom gateways
📚 Documentation
PHASE2_PAYMENT_DISPLAY.md- Complete Phase 2 documentation
📋 Summary of October 28, 2025 Progress
✅ Completed Features
-
Payment Gateway Integration (Phase 1)
- Auto-trigger
process_payment()for admin orders - Session initialization (cart + session)
- Universal gateway support
- Error handling and logging
- Auto-trigger
-
Order Totals Calculation
- Shipping cost from method settings
- Coupon discount calculation
- Correct totals sent to payment gateway
-
Order Totals Display
- Complete breakdown in order form
- Real-time shipping cost
- Professional UI
-
Payment Display (Phase 2)
- Payment metadata extraction API
- Payment Instructions card
- Auto-formatting (timestamps, currency, URLs)
- Multi-gateway support
📊 Files Changed
Backend:
includes/Api/OrdersController.php- 7 major changes
Frontend:
admin-spa/src/routes/Orders/partials/OrderForm.tsx- Totals displayadmin-spa/src/routes/Orders/Detail.tsx- Payment Instructions card
Documentation:
PAYMENT_GATEWAY_INTEGRATION.mdPAYMENT_GATEWAY_PATTERNS.mdORDER_TOTALS_FIX.mdORDER_TOTALS_DISPLAY.mdPHASE2_PAYMENT_DISPLAY.md
🎯 Next Phase: Actions
Phase 3 Features (Planned):
- "Retry Payment" button
- "Cancel Payment" button
- Manual payment status sync
- Payment status webhooks
- Real-time payment updates
Last synced: 2025‑10‑28 22:00 GMT+7
Next milestone: Phase 3 Payment Actions OR Dashboard module development.
🔄 Phase 3: Payment Actions — October 28, 2025 (Night)
✅ Retry Payment Feature - COMPLETE
Goal: Allow admins to manually retry payment processing for orders with payment issues.
🎯 Solution Implemented
Backend: Retry Payment Endpoint
File: includes/Api/OrdersController.php
New endpoint (lines 100-105):
register_rest_route('woonoow/v1', '/orders/(?P<id>\d+)/retry-payment', [
'methods' => 'POST',
'callback' => [__CLASS__, 'retry_payment'],
'permission_callback' => function () { return current_user_can('manage_woocommerce'); },
]);
New method: retry_payment() (lines 1676-1726):
public static function retry_payment( WP_REST_Request $req ): WP_REST_Response {
// Validate permissions
if ( ! current_user_can( 'manage_woocommerce' ) ) {
return new WP_REST_Response( [ 'error' => 'forbidden' ], 403 );
}
// Get order
$order = wc_get_order( $id );
if ( ! $order ) {
return new WP_REST_Response( [ 'error' => 'not_found' ], 404 );
}
// Validate payment method exists
$payment_method = $order->get_payment_method();
if ( empty( $payment_method ) ) {
return new WP_REST_Response( [
'error' => 'no_payment_method',
'message' => __( 'Order has no payment method', 'woonoow' )
], 400 );
}
// Only allow retry for pending/on-hold/failed orders
$status = $order->get_status();
if ( ! in_array( $status, [ 'pending', 'on-hold', 'failed' ] ) ) {
return new WP_REST_Response( [
'error' => 'invalid_status',
'message' => sprintf(
__( 'Cannot retry payment for order with status: %s', 'woonoow' ),
$status
)
], 400 );
}
// Add order note
$order->add_order_note( __( 'Payment retry requested via WooNooW Admin', 'woonoow' ) );
$order->save();
// Trigger payment processing
self::process_payment_gateway( $order, $payment_method );
return new WP_REST_Response( [
'success' => true,
'message' => __( 'Payment processing retried', 'woonoow' )
], 200 );
}
Features:
- ✅ Permission check (manage_woocommerce)
- ✅ Order validation
- ✅ Payment method validation
- ✅ Status validation (only pending/on-hold/failed)
- ✅ Order note for audit trail
- ✅ Reuses existing
process_payment_gateway()method - ✅ Error handling with i18n messages
Frontend: Retry Payment Button
File: admin-spa/src/routes/Orders/Detail.tsx
Added mutation (lines 113-130):
// Mutation for retry payment
const retryPaymentMutation = useMutation({
mutationFn: () => api.post(`/orders/${id}/retry-payment`, {}),
onSuccess: () => {
showSuccessToast(__('Payment processing retried'));
q.refetch();
},
onError: (err: any) => {
showErrorToast(err, __('Failed to retry payment'));
},
});
function handleRetryPayment() {
if (!id) return;
if (confirm(__('Retry payment processing for this order?'))) {
retryPaymentMutation.mutate();
}
}
Added button in Payment Instructions card (lines 234-253):
<div className="px-4 py-3 border-b font-medium flex items-center justify-between">
<div className="flex items-center gap-2">
<Ticket className="w-4 h-4" />
{__('Payment Instructions')}
</div>
{['pending', 'on-hold', 'failed'].includes(order.status) && (
<button
onClick={handleRetryPayment}
disabled={retryPaymentMutation.isPending}
className="text-xs px-3 py-1.5 border rounded-md hover:bg-gray-50 flex items-center gap-1.5 disabled:opacity-50"
title={__('Retry payment processing')}
>
{retryPaymentMutation.isPending ? (
<Loader2 className="w-3 h-3 animate-spin" />
) : (
<RefreshCw className="w-3 h-3" />
)}
{__('Retry Payment')}
</button>
)}
</div>
Features:
- ✅ Only shows for pending/on-hold/failed orders
- ✅ Confirmation dialog before retry
- ✅ Loading state with spinner
- ✅ Disabled during processing
- ✅ Success/error toast notifications
- ✅ Auto-refresh order data after retry
- ✅ Full i18n support
📊 How It Works
Flow:
1. Admin views order with pending payment
↓
2. Payment Instructions card shows "Retry Payment" button
↓
3. Admin clicks button → Confirmation dialog
↓
4. Frontend calls POST /orders/{id}/retry-payment
↓
5. Backend validates order status & payment method
↓
6. Backend adds order note
↓
7. Backend calls process_payment_gateway()
↓
8. Gateway creates new transaction/payment
↓
9. Frontend shows success toast
↓
10. Order data refreshes with new payment metadata
🎯 Use Cases
When to use Retry Payment:
-
Payment Gateway Timeout
- Initial payment failed due to network issue
- Gateway API was temporarily down
- Retry creates new transaction
-
Expired Payment
- VA number expired
- QR code expired
- Retry generates new payment code
-
Failed Transaction
- Customer payment failed
- Need to generate new payment link
- Retry creates fresh transaction
-
Admin Error
- Wrong payment method selected initially
- Need to regenerate payment instructions
- Retry with correct gateway
📊 Example Display
Order Detail - Pending Payment:
┌─────────────────────────────────────────────────┐
│ 🎫 Payment Instructions [🔄 Retry Payment] │
├─────────────────────────────────────────────────┤
│ Payment Code 8808123456789012 (expired) │
│ Reference T1234567890 │
│ Expires At Oct 28, 2025 3:47 PM (past) │
│ Amount Rp137.000 │
└─────────────────────────────────────────────────┘
After clicking Retry Payment:
✅ Payment processing retried
┌─────────────────────────────────────────────────┐
│ 🎫 Payment Instructions [🔄 Retry Payment] │
├─────────────────────────────────────────────────┤
│ Payment Code 8808987654321098 (new!) │
│ Reference T9876543210 │
│ Expires At Oct 29, 2025 11:59 PM │
│ Amount Rp137.000 │
└─────────────────────────────────────────────────┘
📊 Features Summary
Backend:
- ✅ New
/orders/{id}/retry-paymentendpoint - ✅ Status validation (pending/on-hold/failed only)
- ✅ Payment method validation
- ✅ Order note for audit trail
- ✅ Reuses existing payment gateway logic
- ✅ Full error handling
Frontend:
- ✅ Retry Payment button in Payment Instructions card
- ✅ Conditional display (only for eligible statuses)
- ✅ Confirmation dialog
- ✅ Loading states
- ✅ Toast notifications
- ✅ Auto-refresh after retry
UX:
- ✅ Clear button placement
- ✅ Icon + text label
- ✅ Hover states
- ✅ Disabled state during processing
- ✅ Responsive design
📚 Files Changed
Backend:
includes/Api/OrdersController.php(lines 100-105, 1676-1726)
Frontend:
admin-spa/src/routes/Orders/Detail.tsx(lines 7, 113-130, 234-253)
🎯 Next Steps
Phase 3 Remaining Features:
- "Cancel Payment" button
- Manual payment status sync
- Payment status webhooks
- Real-time payment updates
Last synced: 2025‑10‑28 23:20 GMT+7
Next milestone: Complete Phase 3 (Cancel Payment + Status Sync) OR Dashboard module.
🔧 Phase 3: Fixes & Polish — October 28, 2025 (Night)
✅ Retry Payment Improvements - COMPLETE
Issues Found During Testing:
- ❌ Native confirm() dialog - Not consistent with app design
- ❌ 20-30 second delay when retrying payment (Test 2 & 3)
- ❌ Success toast on error - Shows green even when payment fails (Test 6)
🎯 Fixes Implemented
1. Replace confirm() with Shadcn Dialog
File: admin-spa/src/routes/Orders/Detail.tsx
Added Dialog component (lines 9-10, 60, 124-132, 257-283):
// Import Dialog components
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
// State
const [showRetryDialog, setShowRetryDialog] = useState(false);
// Handlers
function handleRetryPayment() {
if (!id) return;
setShowRetryDialog(true); // Show dialog instead of confirm()
}
function confirmRetryPayment() {
setShowRetryDialog(false);
retryPaymentMutation.mutate();
}
// Dialog UI
<Dialog open={showRetryDialog} onOpenChange={setShowRetryDialog}>
<DialogContent>
<DialogHeader>
<DialogTitle>{__('Retry Payment')}</DialogTitle>
<DialogDescription>
{__('Are you sure you want to retry payment processing for this order?')}
<br />
<span className=\"text-amber-600 font-medium\">
{__('This will create a new payment transaction.')}
</span>
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant=\"outline\" onClick={() => setShowRetryDialog(false)}>
{__('Cancel')}
</Button>
<Button onClick={confirmRetryPayment} disabled={retryPaymentMutation.isPending}>
{retryPaymentMutation.isPending ? (
<Loader2 className=\"w-4 h-4 animate-spin mr-2\" />
) : (
<RefreshCw className=\"w-4 h-4 mr-2\" />
)}
{__('Retry Payment')}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
Result:
- ✅ Professional dialog matching Delete Orders design
- ✅ Warning message about creating new transaction
- ✅ Loading state in dialog button
- ✅ Full i18n support
2. Fix 20-30 Second Delay
Problem: WooCommerce analytics tracking to pixel.wp.com during $order->save()
File: includes/Api/OrdersController.php (lines 1718-1728)
Solution:
// Block WooCommerce analytics tracking during save (prevents 30s delay)
add_filter('pre_http_request', function($preempt, $args, $url) {
if (strpos($url, 'pixel.wp.com') !== false || strpos($url, 'stats.wp.com') !== false) {
return new \WP_Error('http_request_blocked', 'WooCommerce analytics blocked');
}
return $preempt;
}, PHP_INT_MAX, 3);
$order->save(); // Now completes in ~0.02s instead of 30s!
remove_all_filters('pre_http_request');
Result:
- ✅ Retry payment now completes in <1 second
- ✅ 1500x faster (30s → 0.02s)
- ✅ Same fix used in order status updates
3. Fix Error Handling
Problem: Always shows success toast, even when payment gateway fails
File: includes/Api/OrdersController.php (lines 1730-1739)
Before:
self::process_payment_gateway( $order, $payment_method );
return new WP_REST_Response( [
'success' => true,
'message' => __( 'Payment processing retried', 'woonoow' )
], 200 );
After:
// Trigger payment processing and capture result
$result = self::process_payment_gateway( $order, $payment_method );
// Check if payment processing failed
if ( is_wp_error( $result ) ) {
return new WP_REST_Response( [
'error' => 'payment_failed',
'message' => $result->get_error_message()
], 400 );
}
return new WP_REST_Response( [
'success' => true,
'message' => __( 'Payment processing retried', 'woonoow' )
], 200 );
Result:
- ✅ Returns 400 error when payment fails
- ✅ Shows red error toast with actual error message
- ✅ Frontend properly handles errors
📊 Test Results
Before Fixes:
- ⏱️ Retry payment: 20-30 seconds
- ❌ Error shows green success toast
- 🔲 Native browser confirm() dialog
After Fixes:
- ⚡ Retry payment: <1 second (1500x faster!)
- ✅ Error shows red error toast with message
- ✨ Professional Shadcn dialog
📚 Files Changed
Frontend:
admin-spa/src/routes/Orders/Detail.tsx(lines 9-10, 60, 124-132, 257-283)
Backend:
includes/Api/OrdersController.php(lines 1718-1739)
🎯 Cancel Payment Analysis
Question: Should we implement "Cancel Payment" feature?
Research: Analyzed Tripay, Duitku, Xendit, PayPal gateways
Findings:
- ❌ Most Indonesian gateways do NOT support canceling pending payments via API
- ❌ VA numbers and QR codes expire automatically
- ❌ No explicit "cancel transaction" endpoint
- ⚠️ Changing order status to "Cancelled" doesn't cancel gateway transaction
- ⚠️ Customer can still pay after order cancelled (until expiry)
- ✅ Gateways handle expired payments automatically
Decision: Skip "Cancel Payment" feature
Reasons:
- Not supported by major Indonesian gateways
- Would require gateway-specific implementations
- Limited value (payments auto-expire)
- Order status "Cancelled" is sufficient for store management
- Webhooks handle late payments
Alternative: Use order status changes + webhook handling
📊 Dashboard Module Implementation
Date: 2025-10-29 14:45 GMT+7
✅ Complete Dashboard with Dummy Data
Objective: Create a fully functional Dashboard SPA module with dummy data for visualization before connecting to real data sources.
🎯 Key Features Implemented
1. Unified Date Range Control
- Single source of truth for period selection (7/14/30 days)
- Positioned inline with Dashboard title (desktop) / below title (mobile)
- Affects all date-based metrics and charts:
- Revenue, Orders, Avg Order Value stat cards
- Sales Overview chart
- Top Products list
- Top Customers list
- Order Status Distribution
2. Metric Cards with Period Comparison
- Revenue - Total for selected period with comparison
- Orders - Count for selected period with comparison
- Avg Order Value - Calculated from period data
- Conversion Rate - Percentage with change indicator
- Period comparison text: "vs previous 7/14/30 days"
- Color-coded trend indicators (green ↑ / red ↓)
3. Low Stock Alert Banner
- Edge-to-edge amber warning banner
- Positioned between stat cards and chart
- Shows count of products needing attention
- Direct link to Products page
- Fully responsive (stacks on mobile)
- Dark mode support
4. Interactive Sales Overview Chart
- Toggle between Revenue / Orders / Both
- Dual-axis chart (Revenue left, Orders right)
- Proper currency formatting with store settings
- Thousand separator support (e.g., Rp10.200.000)
- Y-axis shows M/K format (millions/thousands)
- Translatable axis labels
- Custom tooltip with formatted values
- Filtered by selected period
5. Interactive Order Status Pie Chart
- Dropdown selector for order statuses
- Thick donut chart with double-ring expansion
- Active state shows selected status
- Hover state with visual feedback
- Center label displays count and status name
- Color-coded status indicators in dropdown
- Smooth transitions
6. Top Products & Customers Tabs
- Single card with tab switcher
- Products tab: Shows top 5 with revenue
- Customers tab: Shows top 5 with total spent
- Product icons/emojis for visual appeal
- "View all" link to respective pages
🔧 Technical Implementation
Files Created/Modified:
admin-spa/src/routes/Dashboard/index.tsx- Main Dashboard componentadmin-spa/src/components/ui/tabs.tsx- Tabs component for Shadcn UI
Key Technologies:
- Recharts 3.3.0 - Chart library
- React hooks - useState, useMemo for performance
- TanStack Query - Ready for real data integration
- Shadcn UI - Select, Tabs components
- Tailwind CSS - Responsive styling
State Management:
const [period, setPeriod] = useState('30');
const [chartMetric, setChartMetric] = useState('both');
const [activeStatus, setActiveStatus] = useState('Completed');
const [hoverIndex, setHoverIndex] = useState<number | undefined>();
Computed Metrics:
const periodMetrics = useMemo(() => {
// Calculate revenue, orders, avgOrderValue
// Compare with previous period
return { revenue, orders, avgOrderValue };
}, [chartData, period]);
🎨 UX Improvements
Currency Formatting
- Uses
formatMoney()with store currency settings - Proper thousand separator (dot for IDR, comma for USD)
- Decimal separator support
- Symbol positioning (left/right/space)
- Example:
Rp344.750.000(Indonesian Rupiah)
Responsive Design
- Desktop: 4-column metric grid, inline controls
- Tablet: 2-column metric grid
- Mobile: Single column, stacked layout
- Low Stock banner adapts to screen size
- Chart maintains aspect ratio
Interactive Elements
- Pie chart responds to dropdown selection
- Hover states on all interactive elements
- Smooth transitions and animations
- Keyboard navigation support
📊 Dummy Data Structure
DUMMY_DATA = {
metrics: { revenue, orders, averageOrderValue, conversionRate },
salesChart: [{ date, revenue, orders }, ...], // 30 days
topProducts: [{ id, name, image, quantity, revenue }, ...],
topCustomers: [{ id, name, orders, totalSpent }, ...],
orderStatusDistribution: [{ name, value, color }, ...],
lowStock: [{ id, name, stock, threshold, status }, ...]
}
🐛 Issues Fixed
- TypeScript activeIndex error - Used spread operator with
as anyto bypass type checking - Currency thousand separator - Added
preferSymbol: trueto force store settings - Pie chart not expanding - Removed key-based re-render, used hover state instead
- Mobile responsiveness - Fixed Low Stock banner layout for mobile
- CSS class conflicts - Removed duplicate
self-*classes, fixedflex-shrink-1toshrink
🎯 Next Steps
Ready for Real Data Integration:
- Replace dummy data with API calls
- Connect to WooCommerce analytics
- Implement date range picker (custom dates)
- Add loading states
- Add error handling
- Add data refresh functionality
Future Enhancements:
- Export data functionality
- More chart types (bar, line, scatter)
- Comparison mode (year-over-year)
- Custom metric cards
- Dashboard customization
📊 Dashboard Submenus Implementation
Date: 2025-11-03 21:05 GMT+7
✅ All Dashboard Report Pages Complete
Objective: Implement all 6 dashboard submenu pages with dummy data, shared components, and full functionality.
🎯 Pages Implemented
1. Revenue Report (/dashboard/revenue)
File: admin-spa/src/routes/Dashboard/Revenue.tsx
Features:
- 4 metric cards (Gross Revenue, Net Revenue, Tax Collected, Refunds)
- Area chart showing revenue over time with gradient fills
- Period selector (7/14/30 days)
- Granularity selector (Daily/Weekly/Monthly)
- 4 tabbed breakdown tables:
- By Product (with refunds and net revenue)
- By Category (with percentage of total)
- By Payment Method (BCA VA, Mandiri VA, GoPay, OVO)
- By Shipping Method (JNE, J&T, SiCepat, Pickup)
- Sortable columns with custom rendering
- Proper currency formatting with store settings
2. Orders Analytics (/dashboard/orders)
File: admin-spa/src/routes/Dashboard/Orders.tsx
Features:
- 4 metric cards (Total Orders, Avg Order Value, Fulfillment Rate, Cancellation Rate)
- Line chart showing orders timeline with status breakdown
- Pie chart for order status distribution (Completed, Processing, Pending, etc.)
- Bar chart for orders by day of week
- Bar chart for orders by hour (24-hour heatmap showing peak times)
- Additional metrics cards (Avg Processing Time, Performance Summary)
- Color-coded status indicators
3. Products Performance (/dashboard/products)
File: admin-spa/src/routes/Dashboard/Products.tsx
Features:
- 4 metric cards (Items Sold, Revenue, Low Stock Items, Out of Stock)
- Top products table with emoji icons (🎧, ⌚, 🔌, etc.)
- Product details: SKU, items sold, revenue, stock, conversion rate
- Category performance table with percentage breakdown
- Stock analysis with 3 tabs:
- Low Stock (products below threshold)
- Out of Stock (unavailable products)
- Slow Movers (no recent sales)
- Highlighted low stock items in amber color
- Last sale date and days since sale tracking
4. Customers Analytics (/dashboard/customers)
File: admin-spa/src/routes/Dashboard/Customers.tsx
Features:
- 4 metric cards (Total Customers, Avg LTV, Retention Rate, Avg Orders/Customer)
- 4 customer segment cards with percentages:
- New Customers (green)
- Returning Customers (blue)
- VIP Customers (purple)
- At Risk (red)
- Customer acquisition line chart (New vs Returning over time)
- Top customers table with segment badges
- Customer details: name, email, orders, total spent, avg order value
- LTV distribution bar chart (5 spending ranges)
- Angled x-axis labels for better readability
5. Coupons Report (/dashboard/coupons)
File: admin-spa/src/routes/Dashboard/Coupons.tsx
Features:
- 4 metric cards (Total Discount, Coupons Used, Revenue with Coupons, Avg Discount/Order)
- Line chart showing coupon usage and discount amount over time
- Coupon performance table with:
- Coupon code (WELCOME10, FLASH50K, etc.)
- Type (Percentage, Fixed Cart, Fixed Product)
- Amount (10%, Rp50.000, etc.)
- Uses count
- Total discount amount
- Revenue generated
- ROI calculation (e.g., 6.1x)
- Sortable by any metric
6. Taxes Report (/dashboard/taxes)
File: admin-spa/src/routes/Dashboard/Taxes.tsx
Features:
- 3 metric cards (Total Tax Collected, Avg Tax per Order, Orders with Tax)
- Line chart showing tax collection over time
- Tax by rate table (PPN 11%)
- Tax by location table:
- Indonesian provinces (DKI Jakarta, Jawa Barat, etc.)
- Orders count per location
- Tax amount per location
- Percentage of total
- Two-column layout for rate and location breakdowns
🔧 Shared Components Created
Files: admin-spa/src/routes/Dashboard/components/
StatCard.tsx
Purpose: Reusable metric card with trend indicators
Features:
- Supports 3 formats: money, number, percent
- Trend indicators (↑ green / ↓ red)
- Period comparison text
- Loading skeleton state
- Icon support (lucide-react)
- Responsive design
Usage:
<StatCard
title="Revenue"
value={344750000}
change={15.3}
icon={DollarSign}
format="money"
period="30"
/>
ChartCard.tsx
Purpose: Consistent chart container with title and actions
Features:
- Title and description
- Action buttons slot (for selectors)
- Loading skeleton state
- Configurable height
- Responsive padding
Usage:
<ChartCard
title="Sales Overview"
description="Revenue over time"
actions={<Select>...</Select>}
>
<ResponsiveContainer>...</ResponsiveContainer>
</ChartCard>
DataTable.tsx
Purpose: Sortable, searchable data table
Features:
- Sortable columns (asc/desc/none)
- Custom cell rendering
- Column alignment (left/center/right)
- Loading skeleton (5 rows)
- Empty state message
- Responsive overflow
- Hover states
Usage:
<DataTable
data={products}
columns={[
{ key: 'name', label: 'Product', sortable: true },
{ key: 'revenue', label: 'Revenue', sortable: true, render: formatMoney }
]}
/>
📊 Dummy Data Files
Files: admin-spa/src/routes/Dashboard/data/
All dummy data structures match the planned REST API responses:
dummyRevenue.ts
- 30 days of chart data
- 8 top products
- 4 categories
- 4 payment methods
- 4 shipping methods
- Overview metrics with comparison
dummyOrders.ts
- 30 days of chart data
- 6 order statuses with colors
- 24-hour breakdown
- 7-day breakdown
- Processing time metrics
dummyProducts.ts
- 8 top products with emojis
- 4 categories
- Stock analysis (4 low, 2 out, 3 slow)
- Conversion rates
- Last sale tracking
dummyCustomers.ts
- 10 top customers
- 30 days acquisition data
- 4 customer segments
- 5 LTV ranges
- Indonesian names and emails
dummyCoupons.ts
- 7 active coupons
- 30 days usage data
- ROI calculations
- Multiple coupon types
dummyTaxes.ts
- 30 days tax data
- PPN 11% rate
- 6 Indonesian provinces
- Location-based breakdown
🎨 Technical Highlights
Recharts Integration:
- Area charts with gradient fills
- Line charts with multiple series
- Bar charts with rounded corners
- Pie charts with custom labels
- Custom tooltips with proper formatting
- Responsive containers
- Legend support
Currency Formatting:
- Uses
formatMoney()with store settings - Proper thousand separator (dot for IDR)
- Decimal separator support
- Symbol positioning
- M/K abbreviations for large numbers
- Example:
Rp344.750.000
Responsive Design:
- Desktop: 4-column grids, side-by-side layouts
- Tablet: 2-column grids
- Mobile: Single column, stacked layouts
- Charts maintain aspect ratio
- Tables scroll horizontally
- Period selectors adapt to screen size
TypeScript:
- Full type safety for all data structures
- Exported interfaces for each data type
- Generic DataTable component
- Type-safe column definitions
- Proper Recharts type handling (with
as anyworkaround)
🗺️ Navigation Integration
File: admin-spa/src/nav/tree.ts
Added dashboard submenus:
{
key: 'dashboard',
label: 'Dashboard',
path: '/',
children: [
{ label: 'Overview', mode: 'spa', path: '/', exact: true },
{ label: 'Revenue', mode: 'spa', path: '/dashboard/revenue' },
{ label: 'Orders', mode: 'spa', path: '/dashboard/orders' },
{ label: 'Products', mode: 'spa', path: '/dashboard/products' },
{ label: 'Customers', mode: 'spa', path: '/dashboard/customers' },
{ label: 'Coupons', mode: 'spa', path: '/dashboard/coupons' },
{ label: 'Taxes', mode: 'spa', path: '/dashboard/taxes' },
],
}
Routing: admin-spa/src/App.tsx
All routes already added:
<Route path="/" element={<Dashboard />} />
<Route path="/dashboard/revenue" element={<DashboardRevenue />} />
<Route path="/dashboard/orders" element={<DashboardOrders />} />
<Route path="/dashboard/products" element={<DashboardProducts />} />
<Route path="/dashboard/customers" element={<DashboardCustomers />} />
<Route path="/dashboard/coupons" element={<DashboardCoupons />} />
<Route path="/dashboard/taxes" element={<DashboardTaxes />} />
🎯 Dummy Data Toggle System
Files:
admin-spa/src/lib/useDummyData.ts- Zustand storeadmin-spa/src/components/DummyDataToggle.tsx- Toggle button
Features:
- Global state with LocalStorage persistence
- Toggle button on all dashboard pages
- Visual indicator (Database vs DatabaseZap icon)
- Works across page navigation
- Ready for real API integration
Usage in pages:
const useDummy = useDummyData();
const data = useDummy ? DUMMY_DATA : realApiData;
📈 Statistics
Files Created: 19
- 7 page components
- 3 shared components
- 6 dummy data files
- 1 dummy data store
- 1 toggle component
- 1 tooltip component
Lines of Code: ~4,000+ Chart Types: Area, Line, Bar, Pie Tables: 15+ with sortable columns Metric Cards: 25+ across all pages
🎯 Next Steps
Immediate:
- ✅ Add dashboard submenus to navigation tree
- ✅ Verify all routes work
- ✅ Test dummy data toggle
- ⏳ Update DASHBOARD_PLAN.md
Short Term:
- Wire to real API endpoints
- Add loading states
- Add error handling
- Add data refresh functionality
- Add export functionality (CSV/PDF)
Long Term:
- Custom date range picker
- Comparison mode (year-over-year)
- Dashboard customization
- Real-time updates
- Advanced filters
Last synced: 2025‑11‑03 21:05 GMT+7
Next milestone: Wire Dashboard to real data OR Products module.# 📊 Dashboard Analytics Implementation — November 4, 2025
✅ COMPLETE - All 7 Analytics Pages with Real Data
Status: Production Ready
Implementation: Full HPOS integration with 5-minute caching
Total Lines: ~1200 lines (AnalyticsController.php)
🎯 Implemented Pages
1. Overview (/analytics/overview)
- ✅ Sales chart (revenue + orders over time) with filled dates
- ✅ Top 5 products by revenue
- ✅ Top 5 customers by spending
- ✅ Order status distribution (pie chart with sorting)
- ✅ Key metrics: Revenue, Orders, Avg Order Value, Conversion Rate
2. Revenue (/analytics/revenue)
- ✅ Revenue chart (gross, net, tax, refunds, shipping)
- ✅ Top 10 products by revenue
- 📋 Revenue by category (TODO)
- 📋 Revenue by payment method (TODO)
- 📋 Revenue by shipping method (TODO)
3. Orders (/analytics/orders)
- ✅ Orders over time (total + by status)
- ✅ Orders by status (sorted by importance)
- ✅ Orders by hour of day (24h breakdown)
- ✅ Orders by day of week
- ✅ Average processing time (human-readable)
- ✅ Fulfillment rate & Cancellation rate
4. Products (/analytics/products)
- ✅ Top 20 products by revenue
- ✅ Stock analysis (low stock, out of stock counts)
- ✅ Average price calculation
- 📋 Conversion rate placeholder (0.00)
5. Customers (/analytics/customers)
- ✅ Top 20 customers by spending
- ✅ New vs Returning customers
- ✅ Customer segments
- ✅ Average LTV (Lifetime Value)
- ✅ Average orders per customer
6. Coupons (/analytics/coupons)
- ✅ Coupon usage chart over time
- ✅ Top coupons by discount amount
- ✅ ROI calculation (Revenue Generated / Discount Given)
- ✅ Coupon performance metrics
7. Taxes (/analytics/taxes)
- ✅ Tax chart over time
- ✅ Total tax collected
- ✅ Average tax per order
- ✅ Orders with tax count
🔧 Key Features Implemented
1. Conversion Rate Calculation
Formula: (Completed Orders / Total Orders) × 100
Example:
- 10 orders total
- 3 completed
- Conversion Rate = 30.00%
Location: AnalyticsController.php lines 383-406
$total_all_orders = 0;
$completed_orders = 0;
foreach ($orderStatusDistribution as $status) {
$total_all_orders += $count;
if ($status->status === 'wc-completed') {
$completed_orders = $count;
}
}
$conversion_rate = $total_all_orders > 0
? round(($completed_orders / $total_all_orders) * 100, 2)
: 0.00;
2. Fill All Dates in Charts
Best Practice: Show all dates in range, even with no data
Implementation: AnalyticsController.php lines 324-358
// Create a map of existing data
$data_map = [];
foreach ($salesChart as $row) {
$data_map[$row->date] = [
'revenue' => round(floatval($row->revenue), 2),
'orders' => intval($row->orders),
];
}
// Fill in ALL dates in the range
for ($i = $days - 1; $i >= 0; $i--) {
$date = date('Y-m-d', strtotime("-{$i} days"));
if (isset($data_map[$date])) {
$revenue = $data_map[$date]['revenue'];
$orders = $data_map[$date]['orders'];
} else {
// No data for this date, fill with zeros
$revenue = 0.00;
$orders = 0;
}
$formatted_sales[] = [
'date' => $date,
'revenue' => $revenue,
'orders' => $orders,
];
}
Benefits:
- ✅ Shows complete timeline (no gaps)
- ✅ Weekends/holidays with no orders are visible
- ✅ Accurate trend visualization
- ✅ Matches Google Analytics, Shopify standards
3. Frontend Improvements
Conversion Rate Display
- ✅ Uses real API data (no dummy fallback)
- ✅ Formatted as percentage with 2 decimals
- ✅ Shows comparison for non-"all time" periods
Low Stock Alert
- ✅ Hides when count is zero
- ✅ Shows actual count from API
- ✅ No dummy data fallback
Location: admin-spa/src/routes/Dashboard/index.tsx
// Conversion rate from real data
const currentConversionRate = data?.metrics?.conversionRate?.today ?? 0;
// Low stock alert - hide if zero
{(data?.lowStock?.length ?? 0) > 0 && (
<div className="alert">
{data?.lowStock?.length ?? 0} products need attention
</div>
)}
4. Chart Visualization
Sales Overview Chart:
- ✅ Area chart for revenue (gradient fill)
- ✅ Line chart with dots for orders
- ✅ Balanced visual hierarchy
- ✅ Professional appearance
Order Status Pie Chart:
- ✅ Sorted by importance (completed first)
- ✅ Auto-selection of first status
- ✅ Interactive hover states
- ✅ Color-coded by status
📊 API Endpoints
All endpoints support caching (5 minutes):
GET /woonoow/v1/analytics/overview?period=30GET /woonoow/v1/analytics/revenue?period=30&granularity=dayGET /woonoow/v1/analytics/orders?period=30GET /woonoow/v1/analytics/products?period=30GET /woonoow/v1/analytics/customers?period=30GET /woonoow/v1/analytics/coupons?period=30GET /woonoow/v1/analytics/taxes?period=30
Period Options: 7, 14, 30, all
🎨 UI/UX Features
- ✅ Period selector (Last 7/14/30 days, All time)
- ✅ Real Data toggle (switches between real and dummy data)
- ✅ Responsive design (mobile-first)
- ✅ Dark mode support
- ✅ Loading states
- ✅ Error handling
- ✅ Empty states
- ✅ Metric cards with comparison
- ✅ Professional charts (Recharts)
- ✅ Consistent styling (Shadcn UI)
📚 Files Changed
Backend (PHP)
includes/Api/AnalyticsController.php- Complete implementation (~1200 lines)includes/Api/Routes.php- 7 new endpoints
Frontend (React/TypeScript)
admin-spa/src/routes/Dashboard/index.tsx- Overview pageadmin-spa/src/routes/Dashboard/Revenue.tsx- Revenue pageadmin-spa/src/routes/Dashboard/Orders.tsx- Orders analyticsadmin-spa/src/routes/Dashboard/Products.tsx- Products analyticsadmin-spa/src/routes/Dashboard/Customers.tsx- Customers analyticsadmin-spa/src/routes/Dashboard/Coupons.tsx- Coupons analyticsadmin-spa/src/routes/Dashboard/Taxes.tsx- Taxes analyticsadmin-spa/src/hooks/useAnalytics.ts- Shared analytics hook
🐛 Fixes Applied
- ✅ Recharts prop warning - Changed from function to string-based
dataKey/nameKey - ✅ Conversion rate dummy data - Now uses real API data
- ✅ Low stock alert - Hides when zero
- ✅ Date gaps in charts - All dates filled with zeros
- ✅ "All time" comparison - Suppressed for all time period
- ✅ Percentage formatting - Consistent 2 decimal places
🎯 Next Steps (Optional Enhancements)
- Revenue by Category - Group products by category
- Revenue by Payment Method - Breakdown by gateway
- Revenue by Shipping Method - Breakdown by shipping
- Product Conversion Rate - Track views → purchases
- Customer Retention Rate - Calculate repeat purchase rate
- Previous Period Comparison - Calculate "yesterday" metrics
- Export to CSV - Download analytics data
- Date Range Picker - Custom date selection
- Real-time Updates - WebSocket or polling
- Dashboard Widgets - Customizable widget system
✅ Success Criteria - ALL MET
- 7 analytics pages implemented
- Real HPOS data integration
- Caching (5 minutes)
- Conversion rate calculation
- Fill all dates in charts
- ROI calculation for coupons
- Responsive design
- Dark mode support
- Error handling
- Loading states
- No dummy data fallbacks in Real Data mode
- Professional UI/UX
Implementation Date: November 4, 2025
Total Development Time: ~6 hours
Status: ✅ Production Ready
Next Milestone: Products module OR Settings module
🚀 Standalone Admin Mode — November 5, 2025
✅ COMPLETE - Three Admin Modes Implemented
Goal: Provide flexible admin interface access with three distinct modes: normal (wp-admin), fullscreen, and standalone.
🎯 Three Admin Modes
1. Normal Mode (wp-admin)
- URL:
/wp-admin/admin.php?page=woonoow - Layout: WordPress admin sidebar + WooNooW SPA
- Use Case: Traditional WordPress admin workflow
- Features:
- WordPress admin bar visible
- WordPress sidebar navigation
- WooNooW SPA in main content area
- Settings submenu hidden (use WooCommerce settings)
2. Fullscreen Mode
- Toggle: Fullscreen button in header
- Layout: WooNooW SPA only (no WordPress chrome)
- Use Case: Focus mode for order processing
- Features:
- Maximized workspace
- Distraction-free interface
- All WooNooW features accessible
- Settings submenu hidden
3. Standalone Mode ✨ NEW
- URL:
https://yoursite.com/admin - Layout: Complete standalone application
- Use Case: Quick daily access, mobile-friendly
- Features:
- Custom login page (
/admin#/login) - WordPress authentication integration
- Settings submenu visible (SPA settings pages)
- "WordPress" button to access wp-admin
- "Logout" button in header
- Admin bar link in wp-admin to standalone
- Custom login page (
🔧 Implementation Details
Backend Changes
File: includes/Admin/StandaloneAdmin.php
- Handles
/adminand/admin/requests - Renders standalone HTML template
- Localizes
WNW_CONFIGwithstandaloneMode: true - Provides authentication state
- Includes store settings (currency, formatting)
File: includes/Admin/Menu.php
- Added admin bar link to standalone mode
- Icon:
dashicons-store - Only visible to users with
manage_woocommercecapability
File: includes/Api/AuthController.php
- Login endpoint using native WordPress authentication
- Sequence:
wp_authenticate()→wp_clear_auth_cookie()→wp_set_current_user()→wp_set_auth_cookie()→do_action('wp_login') - Ensures session persistence between standalone and wp-admin
Frontend Changes
File: admin-spa/src/App.tsx
AuthWrappercomponent handles authentication- Login/logout flow with page reload
- "WordPress" button in header (standalone only)
- "Logout" button in header (standalone only)
File: admin-spa/src/routes/Login.tsx
- Custom login form
- Username/password authentication
- Redirects to dashboard after login
- Page reload to pick up fresh cookies/nonces
File: admin-spa/src/nav/tree.ts
- Dynamic settings submenu using getter
- Only shows in standalone mode:
get children() { return isStandalone ? [...] : [] } - Dashboard path:
/dashboard(with redirect from/)
📊 Navigation Structure
Standalone Mode Settings:
Settings
├── WooNooW (main settings)
├── General (store settings)
├── Payments (gateways)
├── Shipping (zones, methods)
├── Products (inventory)
├── Tax (rates)
├── Accounts & Privacy
├── Emails (templates)
├── Advanced (bridge to wp-admin)
├── Integration (bridge to wp-admin)
├── Status (bridge to wp-admin)
└── Extensions (bridge to wp-admin)
Strategy: Option A - Everyday Use Dashboard
- Focus on most-used settings
- Bridge to wp-admin for advanced settings
- Extensible for 3rd party plugins
- Coexist with WooCommerce
🔐 Authentication Flow
Standalone Login:
- User visits
/admin - Not authenticated → redirect to
/admin#/login - Submit credentials →
POST /wp-json/woonoow/v1/auth/login - Backend sets WordPress auth cookies
- Page reload → authenticated state
- Access all WooNooW features
Session Persistence:
- Login in standalone → logged in wp-admin ✅
- Login in wp-admin → logged in standalone ✅
- Logout in standalone → logged out wp-admin ✅
- Logout in wp-admin → logged out standalone ✅
📱 Cross-Navigation
From Standalone to wp-admin:
- Click "WordPress" button in header
- Opens
/wp-adminin same tab - Session persists
From wp-admin to Standalone:
- Click "WooNooW" in admin bar
- Opens
/adminin same tab - Session persists
✅ Features Completed
- Standalone mode routing (
/admin) - Custom login page
- WordPress authentication integration
- Session persistence
- Settings submenu (standalone only)
- WordPress button in header
- Logout button in header
- Admin bar link to standalone
- Dashboard path consistency (
/dashboard) - Dynamic navigation tree
- Settings placeholder pages
- Documentation updates
📚 Documentation
STANDALONE_ADMIN_SETUP.md- Setup guidePROJECT_BRIEF.md- Updated Phase 4PROJECT_SOP.md- Section 7 (modes explanation)PROGRESS_NOTE.md- This section
Implementation Date: November 5, 2025
Status: ✅ Production Ready
Next Milestone: Implement General/Payments/Shipping settings pages
📱 Mobile Orders UI Enhancement & Contextual Headers
Date: November 8, 2025
Status: ✅ Completed & Documented
Overview
Enhanced the Orders module with a complete mobile-first redesign, implementing industry-standard patterns for card layouts, filtering, and contextual headers across all CRUD pages.
Features Implemented
1. Mobile Orders List Redesign ✅
- Card-based layout for mobile (replaces table)
- OrderCard component with status-colored badges
- SearchBar component with integrated filter button
- FilterBottomSheet for mobile-friendly filtering
- Pull-to-refresh functionality
- Infinite scroll support
- Responsive design (cards on mobile, table on desktop)
Files:
admin-spa/src/routes/Orders/index.tsx- Complete mobile redesignadmin-spa/src/routes/Orders/components/OrderCard.tsx- Card componentadmin-spa/src/routes/Orders/components/SearchBar.tsx- Search with filter buttonadmin-spa/src/routes/Orders/components/FilterBottomSheet.tsx- Mobile filter UI
2. OrderCard Design Evolution ✅
Final Design:
┌─────────────────────────────────┐
│ ☐ [#337] Nov 04, 2025, 11:44 PM│ ← Order ID badge (status color)
│ Dwindi Ramadhana →│ ← Customer (bold)
│ 1 item · Test Digital │ ← Items
│ Rp64.500 │ ← Total (large, primary)
└─────────────────────────────────┘
Features:
- Order ID as colored badge (replaces icon)
- Status colors: Green (completed), Blue (processing), Amber (pending), etc.
- Compact layout with efficient space usage
- Touch-optimized tap targets
- Inspired by Uber, DoorDash, Airbnb patterns
3. Filter Bottom Sheet ✅
Features:
- Z-index layering: Above FAB and bottom nav
- Instant filtering (no Apply button)
- Clear all filters button (when filters active)
- Proper padding for bottom navigation
- Scrollable content area
UX Pattern:
- Filters apply immediately on change
- "Clear all filters" button only when filters active
- Follows industry standards (Gmail, Amazon, Airbnb)
4. DateRange Component Fixes ✅
Issues Fixed:
- Horizontal overflow in bottom sheet
- WP forms.css overriding styles
- Redundant Apply button
Solution:
- Vertical layout (
flex-col) - Full shadcn/ui styling with
!importantoverrides - Instant filtering on date change
5. Mobile Contextual Header Pattern ✅
Concept: Dual Header System
-
Contextual Header (Mobile + Desktop)
- Format:
[Back] Page Title [Action] - Common actions (Back, Edit, Save, Create)
- Always visible (sticky)
- Format:
-
Page Header (Desktop Only)
- Extra actions (Print, Invoice, Label)
- Hidden on mobile (
hidden md:flex)
Implementation:
| Page | Contextual Header | Page Header |
|---|---|---|
| Orders List | None | Filters, Search |
| Order Detail | [Back] Order #337 [Edit] | Print, Invoice, Label |
| New Order | [Back] New Order [Create] | None |
| Edit Order | [Back] Edit Order #337 [Save] | None |
Files:
admin-spa/src/routes/Orders/Detail.tsx- Contextual header with Back + Editadmin-spa/src/routes/Orders/New.tsx- Contextual header with Back + Createadmin-spa/src/routes/Orders/Edit.tsx- Contextual header with Back + Saveadmin-spa/src/routes/Orders/partials/OrderForm.tsx- formRef + hideSubmitButton props
Form Submit Pattern:
// Trigger form submit from header button
const formRef = useRef<HTMLFormElement>(null);
const actions = (
<Button onClick={() => formRef.current?.requestSubmit()}>
{mutation.isPending ? 'Saving...' : 'Save'}
</Button>
);
<OrderForm formRef={formRef} hideSubmitButton={true} />
6. Code Quality ✅
ESLint Fixes:
- Fixed React hooks rule violations
- Fixed TypeScript type mismatches
- Fixed React Compiler memoization warnings
- Zero errors, zero warnings in modified files
Files Fixed:
admin-spa/src/routes/Orders/components/OrderCard.tsx- Type fixesadmin-spa/src/routes/Orders/Edit.tsx- Hooks order fixadmin-spa/src/routes/Orders/index.tsx- Memoization fix
Technical Implementation
Key Patterns:
-
usePageHeader Hook
const { setPageHeader, clearPageHeader } = usePageHeader(); useEffect(() => { setPageHeader('Page Title', <Actions />); return () => clearPageHeader(); }, [dependencies]); -
Form Ref Pattern
const formRef = useRef<HTMLFormElement>(null); <form ref={formRef} onSubmit={handleSubmit}> -
Instant Filtering
// No Apply button - filters apply on change useEffect(() => { applyFilters(); }, [filterValue]); -
Responsive Actions
{/* Desktop only */} <div className="hidden md:flex gap-2"> <button>Print</button> <button>Invoice</button> </div>
Benefits
✅ Mobile-First UX
- Card-based layouts for better mobile experience
- Touch-optimized controls and spacing
- Instant filtering without Apply buttons
✅ Consistent Patterns
- All CRUD pages follow same header structure
- Predictable navigation (Back button always visible)
- Loading states in action buttons
✅ Industry Standards
- Follows patterns from Gmail, Amazon, Airbnb
- Modern mobile app-like experience
- Professional, polished UI
✅ Code Quality
- Zero eslint errors/warnings
- Type-safe implementations
- Follows React best practices
Documentation Updates
- ✅
PROJECT_SOP.md- Added section 5.8 (Mobile Contextual Header Pattern) - ✅
PROGRESS_NOTE.md- This entry - ✅ Code comments and examples in implementation
Git Commits
refine: Polish mobile Orders UI based on feedback- OrderCard improvementsfeat: OrderCard redesign and CRUD header improvements- Order ID badge patternfeat: Move action buttons to contextual headers for CRUD pages- Contextual headersfix: Correct Order Detail contextual header implementation- Detail page fixfix: Resolve eslint errors in Orders components- Code quality
Testing Checklist
- OrderCard displays correctly on mobile
- Filter bottom sheet works without overlap
- DateRange component doesn't overflow
- Contextual headers show on all CRUD pages
- Back buttons navigate correctly
- Save/Create buttons trigger form submit
- Loading states display properly
- Desktop extra actions hidden on mobile
- ESLint passes with zero errors/warnings
Next Steps
- Apply contextual header pattern to Products module
- Apply contextual header pattern to Customers module
- Apply contextual header pattern to Coupons module
- Create reusable CRUD page template
- Document pattern in developer guide
Implementation Date: November 8, 2025
Status: ✅ Production Ready
Next Milestone: Apply mobile patterns to other modules
🔔 Notification System Refinement — November 11, 2025
✅ COMPLETE - UI/UX Improvements & Toggle Logic Fixes
Goal: Simplify notification settings UI and fix critical toggle bugs.
🎯 Phase 1: UI/UX Refinements
Channels Page Improvements
Changes Made:
- ✅ Removed redundant "Active/Inactive" badge (color indicates state)
- ✅ Renamed "Built-in Channels" → "Channels" (unified card)
- ✅ Moved "Built-in" badge inline with channel title
- ✅ Removed redundant "Subscribe" toggle for push notifications
- ✅ Unified enable/disable toggle for all channels
- ✅ Auto-subscribe when enabling push channel
- ✅ Green icon when enabled, gray when disabled
Layout:
┌─────────────────────────────────────────┐
│ Channels │
├─────────────────────────────────────────┤
│ 📧 Email [Built-in] │
│ Email notifications powered by... │
│ [Enabled ●] [Configure] │
├─────────────────────────────────────────┤
│ 🔔 Push Notifications [Built-in] │
│ Browser push notifications... │
│ [Disabled ○] [Configure] │
└─────────────────────────────────────────┘
Events Page Improvements
Changes Made:
- ✅ Removed event-level toggle (reduced visual density)
- ✅ Cleaner header layout
- ✅ Focus on per-channel toggles only
Before:
Order Placed [Toggle]
├─ Email [Toggle] Admin
└─ Push [Toggle] Admin
After:
Order Placed
├─ Email [Toggle] Admin
└─ Push [Toggle] Admin
🐛 Phase 2: Critical Bug Fixes
Issue 1: Toggle Not Saving
Problem: Channel toggle always returned enabled: true, changes weren't saved
Root Cause: Backend using get_param() instead of get_json_params()
Fix:
// Before
$channel_id = $request->get_param('channelId');
$enabled = $request->get_param('enabled');
// After
$params = $request->get_json_params();
$channel_id = isset($params['channelId']) ? $params['channelId'] : null;
$enabled = isset($params['enabled']) ? $params['enabled'] : null;
Result: Toggle state now persists correctly ✅
Issue 2: Multiple API Calls
Problem: Single toggle triggered 3 network requests
Root Cause: Optimistic update + onSettled refetch caused race condition
Fix:
// Removed optimistic update
// Now uses server response directly
onSuccess: (data, variables) => {
queryClient.setQueryData(['notification-channels'], (old) =>
old.map(channel =>
channel.id === variables.channelId
? { ...channel, enabled: data.enabled }
: channel
)
);
}
Result: Only 1 request per toggle ✅
Issue 3: Wrong Event Channel Defaults
Problem:
- Email showing as enabled by default (should be disabled)
- Push showing as disabled (inconsistent)
- Backend path was wrong
Root Cause:
- Wrong path:
$settings['event_id']instead of$settings['event_id']['channels'] - Defaults set to
trueinstead offalse
Fix:
// Before
'channels' => $settings['order_placed'] ?? ['email' => ['enabled' => true, ...]]
// After
'channels' => $settings['order_placed']['channels'] ?? [
'email' => ['enabled' => false, 'recipient' => 'admin'],
'push' => ['enabled' => false, 'recipient' => 'admin']
]
Result: Events page shows correct defaults ✅
Issue 4: Events Cannot Be Enabled
Problem: All event channels disabled and cannot be enabled
Root Cause: Wrong data structure in update_event()
Fix:
// Before
$settings[$event_id][$channel_id] = [...];
// Saved as: { "order_placed": { "email": {...} } }
// After
$settings[$event_id]['channels'][$channel_id] = [...];
// Saves as: { "order_placed": { "channels": { "email": {...} } } }
Result: Event toggles save correctly ✅
📊 Data Structure
Correct Structure:
[
'order_placed' => [
'channels' => [
'email' => ['enabled' => true, 'recipient' => 'admin'],
'push' => ['enabled' => false, 'recipient' => 'admin']
]
]
]
🎯 Phase 3: Push Notification URL Strategy
Question: Should push notification URL be static or dynamic?
Answer: Dynamic based on context for better UX
Recommended Approach:
// Event-specific URLs
$notification_urls = [
'order_placed' => '/wp-admin/admin.php?page=woonoow#/orders/{order_id}',
'order_completed' => '/wp-admin/admin.php?page=woonoow#/orders/{order_id}',
'low_stock' => '/wp-admin/admin.php?page=woonoow#/products/{product_id}',
'out_of_stock' => '/wp-admin/admin.php?page=woonoow#/products/{product_id}',
'new_customer' => '/wp-admin/admin.php?page=woonoow#/customers/{customer_id}',
];
Benefits:
- ✅ Better UX - Direct navigation to relevant page
- ✅ Context-aware - Order notification → Order detail
- ✅ Actionable - User can immediately take action
- ✅ Professional - Industry standard (Gmail, Slack, etc.)
Implementation Plan:
- Add
notification_urlfield to push settings - Support template variables:
{order_id},{product_id},{customer_id} - Per-event URL configuration in Templates page
- Default fallback:
/wp-admin/admin.php?page=woonoow#/orders
Current State:
- Global URL in push configuration:
/wp-admin/admin.php?page=woonoow#/orders - Recommendation: Keep as default, add per-event override in Templates
📚 Documentation Created
-
NOTIFICATION_LOGIC.md - Complete logic explanation
- Toggle hierarchy
- Decision logic with examples
- Implementation details
- Usage examples
- Testing checklist
-
NotificationManager.php - Backend validation class
is_channel_enabled()- Global stateis_event_channel_enabled()- Event stateshould_send_notification()- Combined validationsend()- Notification sending
✅ Testing Results
Channels Page:
- Toggle email off → Stays off ✅
- Toggle email on → Stays on ✅
- Toggle push off → Does NOT affect email ✅
- Toggle push on → Does NOT affect email ✅
- Reload page → States persist ✅
Events Page:
- Enable email for "Order Placed" → Saves ✅
- Enable push for "Order Placed" → Saves ✅
- Disable email → Does NOT affect push ✅
- Reload page → States persist ✅
- Enable multiple events → All save independently ✅
Network Tab:
- Each toggle = 1 request only ✅
- Response includes correct
enabledvalue ✅ - No race conditions ✅
📊 Files Changed
Backend:
includes/Api/NotificationsController.php- 3 methods fixedincludes/Core/Notifications/NotificationManager.php- New class
Frontend:
admin-spa/src/routes/Settings/Notifications/Channels.tsx- UI simplified, mutation fixedadmin-spa/src/routes/Settings/Notifications/Events.tsx- Event-level toggle removed
Documentation:
NOTIFICATION_LOGIC.md- Complete logic documentation
🎯 Next Steps
Immediate:
- Implement dynamic push notification URLs per event
- Add URL template variables support
- Add per-event URL configuration in Templates page
Future:
- Push notification icon per event type
- Push notification image per event (product image, customer avatar)
- Rich notification content (order items, product details)
- Notification actions (Mark as read, Quick reply)
Implementation Date: November 11, 2025
Status: ✅ Production Ready
Next Milestone: Dynamic push notification URLs