Skip to content

Mastering Odoo Kanban Color Counters: Boost Your Productivity with Dynamic Visuals in Odoo 18

  • owl
odoo kanban color counters total score 7

Introduction: Transforming Your Odoo Kanban Experience

Welcome back to our Odoo development series! In today’s deep dive, we’re tackling a powerful enhancement that can significantly improve your team’s efficiency and visibility: Odoo Kanban Color Counters. Imagine a Kanban board where critical information doesn’t require a single click. Overdue tasks flash red, urgent items glow orange, completed tasks turn green, and unread messages are immediately visible. This isn’t just a dream; it’s a practical, real-time solution we’ll build using Odoo 18 and the modern OWL 2 framework.

This guide will walk you through the process of developing a custom Odoo 18 module to add dynamic color coding, live counters, and informative tooltips directly to your Kanban cards. Whether you’re managing CRM leads, project tasks, or any other workflow, these visual indicators will provide instant insights, reduce clicks, and make your Odoo experience more intuitive and user-friendly.

The concepts and code presented here are based on a detailed video tutorial. For a visual walkthrough, you can reference the source video here: Odoo OWL 2 Development Add Live Colors & Counters to Kanban Cards.

Why Dynamic Odoo Kanban Color Counters Are Essential

Kanban boards are a cornerstone of modern project management and workflow visualization in Odoo. They offer a clear overview of tasks and opportunities moving through different stages. However, a standard Kanban view can sometimes lack immediate visual cues about a card’s status. Is a task overdue? Does an opportunity have unread messages? How many activities are pending? Without opening each record, these questions often remain unanswered, leading to:

  • Increased Clicks and Time Waste: Users have to open individual records to check critical details, slowing down their workflow.
  • Delayed Action: Important tasks or urgent communications might be overlooked until it’s too late.
  • Reduced Visibility: The overall “health” of a project or sales pipeline isn’t immediately apparent.

By implementing dynamic Odoo Kanban Color Counters, you transform your static board into a live, reactive dashboard. This means:

  • Instant Insights: Visually identify overdue items, upcoming deadlines, or records needing attention at a glance.
  • Improved Efficiency: Prioritize tasks quickly without needing to drill down into each record.
  • Enhanced User Experience: A more engaging and informative interface makes Odoo more pleasant and productive to use.
  • Proactive Management: Managers and team members can spot potential bottlenecks or critical issues before they escalate.

This level of visual feedback is crucial for dynamic environments like CRM and Project Management, where quick decision-making is key.

Understanding the Architecture: OWL 2 and Beyond

Before we dive into the code, let’s briefly touch upon the technologies involved. Odoo 18 leverages OWL 2 (Odoo Web Library 2) as its modern JavaScript framework for frontend development. OWL 2 is a powerful, modular, and reactive system, similar to popular frameworks like React or Vue, but fully integrated within the Odoo ecosystem. It allows us to build dynamic UI components that seamlessly interact with Odoo’s backend.

Our custom module will consist of three main layers:

  1. Python Backend (Models): A lightweight abstract model to compute metrics (like deadline days, activity counts, unread messages, subtasks) for each record. This logic is accessible via Remote Procedure Calls (RPC).
  2. JavaScript Frontend (OWL 2): This is where the magic happens. An OWL 2 service will scan the Kanban view, fetch metrics from the backend via RPC, and then dynamically apply colors, counters, and tooltips to each card. It will also listen for DOM changes to ensure real-time updates.
  3. CSS Styling (SCSS): Custom styles to define the appearance of the colored stripes and counter footers, ensuring a polished look.

With this architecture, we ensure that our Odoo Kanban Color Counters are both dynamic and efficient.

Tutorial: Building Your Custom Odoo Kanban Color Counters Module

Let’s begin the hands-on development.

Prerequisites:

  • Odoo 18 (Enterprise or Community Edition).
  • Developer Mode Activated in Odoo.
  • Basic familiarity with Odoo module structure, Python, JavaScript, and CSS.
  • A code editor like Visual Studio Code.

Step 1: Module Setup

First, create a new custom module in your Odoo custom addons directory.

  1. Create Module Folder: In your custom modules directory, create a new folder named owl_kanban_color_counters.
  2. Create Essential Files and Folders: Inside owl_kanban_color_counters, create the following structure:
owl_kanban_color_counters/
├── __init__.py
├── __manifest__.py
├── models/
│   └── __init__.py
├── static/
│   ├── description/
│   └── src/
│       ├── js/
│       └── scss/

Step 2: __init__.py Files

These files tell Python how to import modules.

  • owl_kanban_color_counters/__init__.py:
    from . import models
    
  • owl_kanban_color_counters/models/__init__.py:
    from . import kanban_metric_mixin
    

    This line will import our custom Python file later.

Step 3: __manifest__.py

The manifest file defines your module’s metadata, dependencies, and assets.

  • owl_kanban_color_counters/__manifest__.py:
    {
        'name': 'OWL Kanban Color & Counters',
        'version': '18.0.1.0.0',  # Odoo version.major.minor.patch.build
        'summary': 'Dynamic Kanban color coding and live counters using OWL 2',
        'category': 'Web',
        'author': 'Odoistic', # Replace with your name or company
        'depends': ['web', 'crm', 'mail'], # 'project' can be added if needed
        'data': [
            # No data files directly loaded as these are static assets
        ],
        'assets': {
            'web.assets_backend': [
                'owl_kanban_color_counters/static/src/js/okc_boot.js',
                'owl_kanban_color_counters/static/src/js/kanban_color_counters.js',
                'owl_kanban_color_counters/static/src/scss/kanban_color_counters.scss',
            ],
        },
        'installable': True,
        'license': 'LGPL-3',
        'images': ['static/description/icon.png'], # Optional: Add a module icon
    }
    

    Explanation:

    • name: The display name for your module in the Apps menu.
    • version: Follows the Odoo versioning convention.
    • summary: A brief description of what the module does.
    • category: Helps categorize your module. ‘Web’ is appropriate for UI enhancements.
    • author: Your name or company.
    • depends:
      • web: Essential for all Odoo web client functionality and OWL 2.
      • crm: Needed if you plan to use these Odoo Kanban Color Counters for CRM leads.
      • mail: Crucial for tracking unread messages, a key metric for our counters.
      • project: (Optional) Include if you want to extend this functionality to project tasks.
    • assets: This is the modern way to load static files (JS, CSS) in Odoo 18. web.assets_backend ensures these files are loaded in the Odoo backend.
    • installable: Must be True for the module to appear in the Apps list.
    • license: LGPL-3 is a common license for community modules.
    • images: (Optional) Path to an icon for your module.

Step 4: models/kanban_metric_mixin.py (Python Backend)

This file defines an abstract model that computes the metrics we’ll display.

  • owl_kanban_color_counters/models/kanban_metric_mixin.py:
    from odoo import models, fields, api
    
    class KanbanMetricMixin(models.AbstractModel):
        _name = 'kanban.metric.mixin'
        _description = 'Abstract model for computing Kanban metrics'
        _abstract = True # Abstract models do not create database tables
    
        @api.model
        def get_kanban_metrics(self, model_name, ids):
            """
            Fetch pre-record metrics used for coloring and counters in Kanban cards.
            This method is called from the frontend JavaScript via RPC.
            """
            if not ids or model_name not in ('crm.lead', 'project.task'):
                return {}
    
            try:
                ids = [int(i) for i in ids]
            except ValueError:
                return {}
    
            # Use self.env.sudo() to bypass access rights and ensure data is always visible
            # with_context(prefetch_fields=False) can improve performance for large datasets
            records = self.env[model_name].sudo().with_context(prefetch_fields=False).browse(ids)
            today = fields.Date.context_today(self)
            result = {}
    
            for record in records:
                metrics = {}
    
                # 1. Deadline Calculation
                if hasattr(record, 'date_deadline') and record.date_deadline:
                    deadline = record.date_deadline
                    days_until_deadline = (deadline - today).days
                    metrics['deadline_days_left'] = days_until_deadline
    
                # 2. Activities Count
                # Filters activities for upcoming deadlines, not all activities
                if hasattr(record, 'activity_ids'):
                    metrics['activities_count'] = len(record.activity_ids.filtered(lambda a: a.date_deadline >= today))
    
                # 3. Unread Messages Count
                # Excludes internal messages and already read messages
                if hasattr(record, 'message_ids') and hasattr(record, 'message_unread') and record.message_unread:
                    # Note: message_unread field is a boolean, indicating if there are ANY unread messages
                    # To get a count, we iterate through messages
                    metrics['unread_count'] = len(record.message_ids.filtered(lambda m: not m.is_internal and not m.message_read))
    
    
                # 4. Subtasks Count (specific to project.task)
                if model_name == 'project.task' and hasattr(record, 'child_ids'):
                    metrics['subtask_count'] = len(record.child_ids)
                    # A task is "done" if all its child tasks are closed
                    metrics['is_done'] = all(child.stage_id and child.stage_id.is_closed for child in record.child_ids)
    
    
                # 5. "Is Done" Status for CRM Lead
                if model_name == 'crm.lead':
                    metrics['is_done'] = record.probability >= 100 or (hasattr(record.stage_id, 'is_won') and record.stage_id.is_won)
    
                result[record.id] = metrics
    
            return result
    

    Explanation:

    • Abstract Model: _abstract = True means this model doesn’t create a database table. It’s a reusable piece of logic.
    • get_kanban_metrics Method: This is the core method.
      • Input Validation: Ensures ids are provided and the model_name is crm.lead or project.task.
      • self.env[model_name].sudo().browse(ids): Retrieves the records. sudo() is used to bypass potential access right issues, ensuring all users see the same metrics. with_context(prefetch_fields=False) is a performance optimization.
      • Metrics Computation:
        • deadline_days_left: Calculates the remaining days until the deadline (negative if overdue).
        • activities_count: Counts scheduled activities that are not yet past due.
        • unread_count: Counts unread chatter messages. This is particularly useful for Odoo Kanban Color Counters to highlight communication.
        • subtask_count: For project.task records, it counts child tasks and determines if the main task is “done” based on its children.
        • is_done: Determines if a CRM lead is won (probability >= 100% or stage is ‘Won’).
      • Result Structure: Returns a dictionary where keys are record IDs and values are their respective metric dictionaries.

    Step 5: static/src/js/kanban_color_counters.js (OWL 2 Frontend Logic)

    This is the main JavaScript file responsible for rendering the Odoo Kanban Color Counters.

    • owl_kanban_color_counters/static/src/js/kanban_color_counters.js:
      /** @odoo-module */ // Declares this as an Odoo ES module
      
      import { registry } from '@web/core/registry'; // For registering services
      import { useService } from '@web/core/utils/hooks'; // For using Odoo services like RPC
      
      // Register a new service in the 'services' category
      registry.category("services").add('kanban_color_counters_service', { // Renamed to avoid potential conflict
          start(env) { // The 'start' method runs when the service is initialized
      
              // Use the RPC service to make calls to the Odoo backend
              const RPC = useService('rpc');
      
              // Configuration for color thresholds
              const config = {
                  thresholdDueSoon: 2, // Days: Deadline within 2 days is considered 'due soon'
              };
      
              // Helper functions for determining colors, formatting tooltips, etc.
              const colorHelpers = {
                  /**
                   * Determines CSS class and hex color based on record metrics.
                   * Priority: Done > Overdue > Due Soon > Unread
                   * @param {Object} m - The metrics object for a single record.
                   * @returns {Object} - An object with 'class' (CSS class) and 'color' (hex code).
                   */
                  colorFromMatrix: (m) => {
                      if (m.is_done) return { class: 'okc-card-green', color: '#4CAF50' }; // Green for completed/won
                      if (m.deadline_days_left !== undefined && m.deadline_days_left < 0) return { class: 'okc-card-red', color: '#F44336' }; // Red for overdue
                      if (m.deadline_days_left !== undefined && m.deadline_days_left <= config.thresholdDueSoon) return { class: 'okc-card-orange', color: '#FF9800' }; // Orange for due soon
                      if (m.unread_count > 0) return { class: 'okc-card-blue', color: '#2196F3' }; // Blue for unread messages
                      return {}; // No special class/color
                  },
      
                  /**
                   * Determines a fallback color based on the Kanban column title (stage name).
                   * Used when server metrics are not available immediately or for default styling.
                   * @param {string} title - The title of the Kanban column.
                   * @returns {Object} - An object with 'class' (CSS class) and 'color' (hex code).
                   */
                  colorFromColumn: (title) => {
                      title = title.toLowerCase();
                      if (title.includes('won') || title.includes('done') || title.includes('closed')) return { class: 'okc-card-green', color: '#4CAF50' };
                      if (title.includes('proposition') || title.includes('proposal')) return { class: 'okc-card-orange', color: '#FF9800' };
                      if (title.includes('qualified') || title.includes('progress')) return { class: 'okc-card-blue', color: '#2196F3' };
                      if (title.includes('new') || title.includes('draft')) return { class: 'okc-card-red', color: '#F44336' };
                      return { class: 'okc-card-blue', color: '#2196F3' }; // Default color if no keyword matches
                  },
      
                  /**
                   * Formats the metrics into a human-readable tooltip string.
                   * @param {Object} m - The metrics object for a single record.
                   * @returns {string} - The formatted tooltip text.
                   */
                  formatTooltip: (m) => {
                      let tooltip = 'Kanban Card Details:\n';
                      if (m.deadline_days_left !== undefined) {
                          tooltip += `Deadline: `;
                          if (m.deadline_days_left < 0) tooltip += `${Math.abs(m.deadline_days_left)} days overdue\n`;
                          else if (m.deadline_days_left === 0) tooltip += `due today\n`;
                          else tooltip += `${m.deadline_days_left} days left\n`;
                      }
                      if (m.activities_count !== undefined && m.activities_count > 0) tooltip += `Activities: ${m.activities_count} pending\n`;
                      if (m.unread_count !== undefined && m.unread_count > 0) tooltip += `Unread Messages: ${m.unread_count}\n`;
                      if (m.subtask_count !== undefined && m.subtask_count > 0) tooltip += `Subtasks: ${m.subtask_count}\n`;
                      if (m.is_done) tooltip += `Status: Completed/Won\n`;
                      return tooltip.trim(); // Remove trailing newline
                  }
              };
      
              /**
               * Extracts the Odoo record ID from a Kanban card element.
               * Tries multiple common locations where Odoo stores the ID.
               * @param {HTMLElement} card - The Kanban card DOM element.
               * @returns {string|null} The record ID or null if not found.
               */
              const getResId = (card) => {
                  // Try data-id or data-res-id attributes on the card itself
                  let resId = card.dataset.id || card.dataset.resId;
      
                  // If not found, look for a link inside the card with #id= in its URL hash
                  if (!resId) {
                      const aTag = card.querySelector('a[href*="#id="]');
                      if (aTag) {
                          const href = aTag.getAttribute('href');
                          try {
                              // Parse the hash part of the URL
                              const url = new URL(href, window.location.origin); // Use window.location.origin for base
                              const params = new URLSearchParams(url.hash.substring(1)); // Remove the leading '#'
                              resId = params.get('id');
                          } catch (e) {
                              console.warn("Invalid URL found in Kanban card link:", href, e);
                          }
                      }
                  }
      
                  // If still not found, look for data-res-id on an inner clickable element
                  if (!resId) {
                      const innerClickable = card.querySelector('[data-res-id]');
                      if (innerClickable) {
                          resId = innerClickable.dataset.resId;
                      }
                  }
                  return resId;
              };
      
              /**
               * Inserts or updates a colored stripe on the left side of a Kanban card.
               * @param {HTMLElement} card - The Kanban card DOM element.
               * @param {Object} color - An object containing 'class' and 'color' (hex code).
               */
              const ensureStripe = (card, color) => {
                  let stripe = card.querySelector('.okc-stripe');
                  if (!stripe) {
                      stripe = document.createElement('div');
                      stripe.classList.add('okc-stripe');
                      card.prepend(stripe); // Add as the first child
                  }
                  // Apply the new color
                  stripe.style.backgroundColor = color.color || 'transparent';
                  // Add a subtle box-shadow for depth and visibility
                  stripe.style.boxShadow = `inset 4px 0 0 0 ${color.color || 'transparent'}`;
                  // Optional: add outline for contrast against very dark backgrounds
                  stripe.style.outline = `1px solid ${color.color || 'transparent'}`;
              };
      
              /**
               * The main worker function: repaints a single Kanban container.
               * @param {HTMLElement} container - The Kanban view container element.
               */
              const repaintContainer = async (container) => {
                  // Determine the model currently being displayed.
                  // For now, it's hardcoded to 'crm.lead' for immediate CRM functionality.
                  // This could be made dynamic by parsing the view's data.
                  const model = 'crm.lead'; // Example: 'project.task' if targeting project module
      
                  const cards = container.querySelectorAll('.o_kanban_record');
                  const idsToFetch = [];
      
                  cards.forEach(card => {
                      const resId = getResId(card);
                      if (resId) {
                          idsToFetch.push(parseInt(resId, 10)); // Ensure IDs are integers
                      }
                  });
      
                  // First pass: apply immediate column-based colors
                  cards.forEach(card => {
                      const columnTitleElement = card.closest('.o_kanban_group')?.querySelector('.o_kanban_header .o_column_title');
                      const columnTitle = columnTitleElement ? columnTitleElement.innerText : '';
                      const color = colorHelpers.colorFromColumn(columnTitle);
                      ensureStripe(card, color);
                  });
      
                  // If we found any IDs, fetch server metrics
                  if (idsToFetch.length > 0) {
                      try {
                          // Call the Python mixin method via RPC
                          const metrics = await RPC('/web/dataset/call_kw/' + model, { // Correct RPC path for abstract models
                              model: 'kanban.metric.mixin',
                              method: 'get_kanban_metrics',
                              args: [model, idsToFetch],
                              kwargs: {},
                          });
      
                          cards.forEach(card => {
                              const resId = getResId(card);
                              if (!resId || !metrics[resId]) {
                                  // If no metrics are available, column-based color remains
                                  return;
                              }
      
                              const metric = metrics[resId];
      
                              // Remove any previously applied color classes to avoid conflicts
                              card.classList.remove('okc-card-green', 'okc-card-red', 'okc-card-orange', 'okc-card-blue');
      
                              // Try to apply matrix-based color
                              const color = colorHelpers.colorFromMatrix(metric);
                              if (color.class) {
                                  card.classList.add(color.class); // Apply the new color class
                              }
                              ensureStripe(card, color); // Update the stripe color
      
                              // Inject counters footer and tooltip if not already present
                              let footer = card.querySelector('.okc-counters');
                              if (metric && !footer) {
                                  footer = document.createElement('div');
                                  footer.classList.add('okc-counters');
                                  card.appendChild(footer);
                              }
                              if (footer) {
                                  let tooltipText = colorHelpers.formatTooltip(metric);
                                  card.setAttribute('title', tooltipText); // Set the tooltip for the card
                                  // You could add more complex counter displays here within the footer
                                  // For simplicity, we just use the tooltip for the full summary.
                                  // Example: footer.innerHTML = `<span>${metric.activities_count || 0} A | ${metric.unread_count || 0} U</span>`;
                              }
                          });
                      } catch (e) {
                          console.error("Error fetching Kanban metrics:", e);
                      }
                  }
              };
      
              /**
               * Applies the repaint logic to all Kanban views currently on the page.
               */
              const applyToAllKanban = () => {
                  const kanbanContainers = document.querySelectorAll('.o_kanban_view');
                  kanbanContainers.forEach(repaintContainer);
              };
      
              // --- Initialization and Real-time Updates ---
      
              // 1. Initial paint: Apply colors and counters as soon as the service starts
              applyToAllKanban();
      
              // 2. Repaint after short delays: Catch elements rendered lazily by Odoo
              setTimeout(applyToAllKanban, 500);
              setTimeout(applyToAllKanban, 1500);
      
              // 3. Mutation Observer: Recolor whenever the DOM of the Odoo backend changes
              // This catches changes like scrolling, filtering, moving cards, opening/closing forms, etc.
              const observer = new MutationObserver(applyToAllKanban);
              const observerConfig = { childList: true, subtree: true, attributes: false };
              const targetNode = document.querySelector('body'); // Observe the entire body for broad coverage
      
              if (targetNode) {
                  observer.observe(targetNode, observerConfig);
              } else {
                  console.warn("Could not find body element to attach MutationObserver.");
              }
      
              // Cleanup when the service stops (e.g., when navigating away from Odoo client)
              return () => {
                  observer.disconnect();
              };
          },
      });
      

      Explanation:

      • @odoo-module & Service Registration: Declares an ES module and registers a service kanban_color_counters_service. Services are automatically started and stopped by the Odoo web client, making them ideal for background tasks.
      • useService('rpc'): Hooks into Odoo’s RPC service to make calls to the Python backend.
      • colorFromMatrix: This function is central to our Odoo Kanban Color Counters. It applies colors based on the numerical metrics: green for “done,” red for “overdue,” orange for “due soon,” and blue for “unread messages.” The order of conditions defines priority.
      • colorFromColumn: Provides a fallback color based on the Kanban column (stage) name. This ensures cards always have some visual indicator, even if backend metrics aren’t immediately available or applicable.
      • formatTooltip: Generates a detailed tooltip that summarizes all the computed metrics.
      • getResId: A robust function to reliably extract the Odoo record ID from various places within a Kanban card’s DOM structure.
      • ensureStripe: Dynamically adds or updates a vertical colored stripe on the left side of each Kanban card, using the color determined by our helper functions.
      • repaintContainer: The heart of the frontend logic.
        • It identifies all Kanban cards within a specific container.
        • First, it applies column-based fallback colors.
        • Then, it collects all record IDs and makes an RPC call to get_kanban_metrics on our Python mixin.
        • Upon receiving metrics, it iterates through cards again, removes old colors, applies the more precise matrix-based colors, and injects/updates tooltips.
      • applyToAllKanban: Orchestrates the process by finding all Kanban views on the page and calling repaintContainer for each.
      • Real-time Updates:
        • Initial applyToAllKanban() calls: Ensure cards are colored immediately and catch any elements that might load slightly later.
        • MutationObserver: This powerful Web API is key for real-time responsiveness. It monitors the DOM for changes (e.g., new cards appearing after a search, filtering, or moving cards) and triggers applyToAllKanban() to refresh the colors and counters, making our Odoo Kanban Color Counters truly dynamic.

    Step 6: static/src/js/okc_boot.js (Boot Service)

    This simple file creates a boot service, primarily used to ensure our module’s assets are loaded correctly.

    • owl_kanban_color_counters/static/src/js/okc_boot.js:
      /** @odoo-module */
      
      import { registry } from '@web/core/registry';
      
      registry.category("services").add('okc_boot', {
          start(env) {
              // This service is primarily to ensure that the module's assets (JS and CSS)
              // are properly injected and loaded in the Odoo web client.
              // No specific logic is needed here for this module, as the main logic is
              // in kanban_color_counters.js, which is also loaded as an asset.
          },
      });
      

      Explanation: A minimalistic service that simply registers itself. Its presence in web.assets_backend within __manifest__.py helps ensure the proper loading order and injection of our assets into the Odoo web client.

    Step 7: static/src/scss/kanban_color_counters.scss (CSS Styling)

    This file defines the visual styles for our Kanban cards and the new elements.

    • owl_kanban_color_counters/static/src/scss/kanban_color_counters.scss:
      // Styles for the custom Odoo Kanban Color Counters
      
      .o_kanban_view {
          // Target individual Kanban records (cards)
          .o_kanban_record {
              position: relative; // Needed for absolute positioning of the stripe
              border-left: 0px solid transparent; // Initial state, stripe will be managed by JS
              box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.1); // Subtle shadow for depth
              transition: border-color 0.3s ease, box-shadow 0.3s ease; // Smooth transitions for visual changes
              overflow: hidden; // Ensures stripe doesn't bleed out if borders are rounded
          }
      
          // Style for the dynamically inserted color stripe
          .okc-stripe {
              position: absolute;
              top: 0;
              left: 0;
              bottom: 0;
              width: 5px; // Width of the color stripe
              z-index: 1; // Ensure stripe is above other card content if needed
          }
      
          /* Color definitions for the stripes and card borders */
      
          .okc-card-green {
              border-left-color: #4CAF50 !important; // Green for 'Done' or 'Won'
          }
      
          .okc-card-orange {
              border-left-color: #FF9800 !important; // Orange for 'Due Soon' or 'Proposition'
          }
      
          .okc-card-red {
              border-left-color: #F44336 !important; // Red for 'Overdue' or 'New'
          }
      
          .okc-card-blue {
              border-left-color: #2196F3 !important; // Blue for 'Unread' or 'Qualified'
          }
      
          /* Styling for the counters footer */
          .okc-counters {
              position: absolute;
              bottom: 0;
              left: 0;
              width: 100%;
              padding: 4px 8px; // Padding inside the footer
              background-color: rgba(255, 255, 255, 0.9); // Slightly transparent white background
              border-top: 1px solid rgba(0, 0, 0, 0.05); // Subtle border
              font-size: 0.75em; // Smaller font size
              color: #555; // Darker text color
              text-align: right; // Align text to the right
              z-index: 2; // Ensure footer is above the stripe
              box-sizing: border-box; // Include padding and border in the element's total width and height
          }
      }
      

      Explanation:

      • .o_kanban_record: Sets up the base styling for Kanban cards, including position: relative which is crucial for positioning our absolute okc-stripe. transition creates a smooth visual effect when colors change.
      • .okc-stripe: Defines the appearance and absolute positioning of the vertical colored bar.
      • .okc-card-green, .okc-card-orange, etc.: These classes, applied by JavaScript, set the border-left-color for the cards, giving them their distinctive visual cues. !important is often used to ensure these custom styles override Odoo’s default styles.
      • .okc-counters: Styles the dynamic footer that will potentially display counters and acts as the anchor for our rich tooltips.

    Step 8: Install the Module

    Once all files are created and saved:

    1. Restart Odoo Service: Go to your terminal and restart your Odoo server (e.g., sudo service odoo-server restart or python3 odoo-bin --addons-path=...).
    2. Update Apps List: In your Odoo instance, navigate to Settings -> Developer Tools (top right, bug icon) -> Update Apps List.
    3. Install Module: Go to the Apps menu, search for “OWL Kanban Color & Counters”, and click Activate.

    Step 9: Test Your Odoo Kanban Color Counters

    After installation, navigate to a Kanban view, for example:

    • CRM -> Pipeline -> Kanban view
    • (If you added project dependency and configured for it) Project -> Projects -> Kanban view

    You should immediately see:

    • Colored Stripes: Kanban cards will have colored stripes on their left side. Initially, these might be based on the column’s stage name.
    • Dynamic Updates: As the page loads, or if you filter, move cards, or interact with the view, you’ll observe cards changing colors based on their underlying metrics.
    • Tooltips: Hover over a card, and you’ll see a detailed tooltip summarizing its deadline, activities, unread messages, and done status, thanks to our robust Odoo Kanban Color Counters.

    Customization and Further Enhancements

    The beauty of this module lies in its flexibility. You can easily adapt it to your specific business needs:

    • Adjust Due Soon Threshold: Modify config.thresholdDueSoon in kanban_color_counters.js to change when a task is considered “due soon.”
    • Customize Stage Colors: Edit the colorFromColumn function in kanban_color_counters.js to match your specific Kanban stage names and desired colors.
    • Extend to More Models: Change the model variable in repaintContainer to 'project.task' or make it dynamic by reading the _name from the Kanban view’s definition. Remember to add the corresponding Odoo module to your depends in __manifest__.py.
    • Add More Metrics: Extend the get_kanban_metrics Python method to calculate any other relevant metrics (e.g., priority, assigned user status) and then update colorFromMatrix and formatTooltip accordingly.
    • Visual Counters in Footer: Currently, we’re using the tooltip for detailed metrics. You could extend the repaintContainer logic to dynamically insert smaller counter badges (e.g., <span>A: ${metric.activities_count}</span>) directly into the .okc-counters footer element.
    • Advanced ID Extraction: In some highly customized Kanban views, getResId might not find the ID. You might need to extend it or even create a tiny Odoo QWeb patch to ensure data-res-id is explicitly stamped on your Kanban cards.

    Conclusion

    Congratulations! You’ve successfully built a powerful custom Odoo 18 module that injects dynamic Odoo Kanban Color Counters and live metrics directly into your Kanban views. By leveraging OWL 2, RPC calls to a Python backend, and a reactive JavaScript frontend, you’ve transformed a standard Kanban board into an insightful, real-time dashboard. This enhancement significantly boosts productivity, reduces information overload, and provides a more engaging user experience for your Odoo users.

    The principles learned here, from service registration to MutationObserver usage, are invaluable for any Odoo 18 frontend development. Continue experimenting, and let these dynamic visuals streamline your workflows!

    If you have any questions, need further customization, or require Odoo implementation support for your business, feel free to reach out to me via contact@odoistic.co.uk. Your support for this channel, via a small PayPal donation (link typically in video description), also helps me continue creating free Odoo content.

    Thanks for watching, and I’ll see you in the next video! Take care.


Discover more from teguhteja.id

Subscribe to get the latest posts sent to your email.

Leave a Reply

WP Twitter Auto Publish Powered By : XYZScripts.com