# LabelEngine

> DB-backed label rebranding. Each product profile maps entity types to domain-specific labels.
> Example: education profile maps `lead → Application`, `deal → Enrollment`, `contact → Student`.

---

## Migration: `entity_domain_maps`

```php
Schema::create('entity_domain_maps', function (Blueprint $table) {
    $table->id();
    $table->string('product_profile');      // 'taskco-crm-education', 'taskco-crm-realstate', etc.
    $table->string('entity_type');          // 'lead', 'deal', 'contact'
    $table->string('label');                // singular display name
    $table->string('label_plural')->nullable();
    $table->timestamps();
    $table->unique(['product_profile', 'entity_type']);
});
```

---

## Service: `LabelResolver`

```php
// CoreApp/app/Services/LabelEngine/LabelResolver.php
namespace CoreApp\Services\LabelEngine;

use CoreApp\Models\LabelEngine\EntityDomainMap;

class LabelResolver
{
    /** Returns label map for given profile. Uses once() for per-request cache. */
    public function resolveAll(string $profile): array
    {
        return once(function () use ($profile) {
            $maps = EntityDomainMap::where('product_profile', $profile)->get();

            $defaults = [
                'lead'    => ['singular' => 'Lead',    'plural' => 'Leads'],
                'deal'    => ['singular' => 'Deal',    'plural' => 'Deals'],
                'contact' => ['singular' => 'Contact', 'plural' => 'Contacts'],
            ];

            return $maps->reduce(fn ($carry, $m) => array_merge($carry, [
                $m->entity_type => [
                    'singular' => $m->label,
                    'plural'   => $m->label_plural ?? ($m->label . 's'),
                ],
            ]), $defaults);
        });
    }
}
```

Bind in `CoreAppServiceProvider::register()`:
```php
$this->app->singleton(LabelResolver::class);
```

---

## Share Labels via `HandleInertiaRequests`

In `app/Http/Middleware/HandleInertiaRequests.php`, add to `share()`:
```php
'labels' => fn () => app(\CoreApp\Services\LabelEngine\LabelResolver::class)
    ->resolveAll(tenant('product_profile') ?? 'taskco-crm-general'),
```

---

## React: `LabelContext` + `useLabel()`

File: `resources/js/contexts/label-context.tsx`

```tsx
import React, { createContext, useContext } from 'react';

interface LabelEntry { singular: string; plural: string; }
interface LabelMap { lead: LabelEntry; deal: LabelEntry; contact: LabelEntry; [key: string]: LabelEntry; }

const defaults: LabelMap = {
    lead:    { singular: 'Lead',    plural: 'Leads'    },
    deal:    { singular: 'Deal',    plural: 'Deals'    },
    contact: { singular: 'Contact', plural: 'Contacts' },
};

const LabelContext = createContext<LabelMap>(defaults);

export function LabelProvider({ labels, children }: { labels: LabelMap; children: React.ReactNode }) {
    return <LabelContext.Provider value={labels}>{children}</LabelContext.Provider>;
}

export function useLabel(entityType: string): LabelEntry {
    const ctx = useContext(LabelContext);
    return ctx[entityType] ?? { singular: entityType, plural: entityType + 's' };
}
```

Mount `LabelProvider` in the root layout/AppLayout — pass `usePage().props.labels`:
```tsx
// In AppLayout or root _app equivalent:
import { LabelProvider } from '@/contexts/label-context';
const { labels } = usePage().props as { labels: any };
// Wrap children:
<LabelProvider labels={labels}>{children}</LabelProvider>
```

---

## Usage in CRM Pages

```tsx
import { useLabel } from '@/contexts/label-context';

function LeadsIndex() {
    const label = useLabel('lead');
    return (
        <h2>{label.plural}</h2>      // "Applications" on education profile
    );
}
```

---

## Frontend Adaptation — Complete Guide

> **Core principle:** Zero profile-specific `if` statements in component code. Every component reads from `useLabel()`. The pack seeds the DB row; the context distributes it.

### Full data flow

```
education_pack.json
  "entity_domain_maps": [{entity_type:"lead", label:"Application", label_plural:"Applications"}]
        ↓  FeaturePackSeeder::run()
entity_domain_maps (tenant DB table)
        ↓  LabelResolver::resolveAll($activeProfileSlug)
HandleInertiaRequests::share('labels', [...])
        ↓  every Inertia page load (lazy — one DB query per request, then cached)
{ lead: {singular:"Application", plural:"Applications"}, ... }
        ↓  AppLayout wraps children in <LabelProvider value={labels}>
useLabel('lead')  →  { singular: "Application", plural: "Applications" }
```

### Page title + heading + breadcrumb

```tsx
// CrmApp/Lead/resources/assets/js/pages/Lead/Index.tsx
export default function Index({ leads }) {
    const { singular, plural } = useLabel('lead');

    return (
        <>
            <Head title={plural} />
            <div className="no-scrollbar rounded-xl bg-gray-100/55 p-2 sm:p-4">
                <div className="grid grid-cols-1 gap-1 mb-4">
                    <h2 className="text-xl font-bold sm:text-2xl">{plural}</h2>
                    <div className="flex items-center text-sm text-gray-600">
                        <span>CRM</span>
                        <span className="mx-2">›</span>
                        <span>{plural}</span>
                    </div>
                </div>
                {/* DataTable, etc. */}
            </div>
        </>
    );
}

Index.layout = (page: ReactNode) => {
    const { singular, plural } = useLabel('lead');   // ← dynamic
    return (
        <AppLayout
            title={plural}
            breadcrumbs={[
                { title: 'Home',  href: '/' },
                { title: 'CRM',   href: '#' },
                { title: plural,  href: '#' },        // "Applications" or "Leads"
            ]}
        >
            {page}
        </AppLayout>
    );
};
```

> **Note:** `useLabel` inside `Index.layout` works because `AppLayout` already wraps children in `<LabelProvider>` before the layout function runs.

### StatisticsCards

```tsx
const { plural } = useLabel('lead');

<StatisticsCard
    title={`Total ${plural}`}           // "Total Applications" or "Total Leads"
    value={stats.total}
    subtitle="+0% from last month"
    subtitleColor="green"
    iconBg="bg-blue-100"
    icon={asset('images/icons/total-leads.svg')}
/>
```

### Action buttons

```tsx
const { singular } = useLabel('lead');
const contactLabel = useLabel('contact');

<Button className="bg-[#008060] text-white hover:bg-[#006b51]">
    Add {singular}                        {/* "Add Application" */}
</Button>

<Button variant="outline">
    Convert to {contactLabel.singular}    {/* "Convert to Student" */}
</Button>
```

### Empty state

```tsx
const { plural } = useLabel('lead');

<div className="py-12 text-center text-gray-500">
    No {plural.toLowerCase()} found.      {/* "No applications found." */}
</div>
```

### Sidebar menu items

```tsx
// resources/js/components/menuItems/menuLists/application-menu-items.tsx
function CrmMenuItems() {
    const leadLabel   = useLabel('lead');
    const dealLabel   = useLabel('deal');
    const contactLabel = useLabel('contact');

    return tenantAccess('apps', 'crm') ? [
        { title: leadLabel.plural,    href: route('leads.index')        },  // "Applications"
        { title: dealLabel.plural,    href: route('deals.index')        },  // "Enrollments"
        { title: 'Board',             href: route('deals.board')        },
        { title: 'Pipelines',         href: route('pipelines.index')    },
        { title: contactLabel.plural, href: route('crm.contacts.index') },  // "Students"
        { title: 'Activity',          href: route('crm.activity.index') },
    ] : [];
}
```

### Show page header (gradient banner)

```tsx
// Lead/Show.tsx
const { singular } = useLabel('lead');
const contactLabel = useLabel('contact');

<div className="overflow-hidden rounded-2xl border border-[#d8d8d8] bg-gradient-to-r from-[#ffffff] via-[#f5fbf8] to-[#eef9f3]">
    <div className="flex flex-col gap-4 p-4 sm:flex-row sm:items-center sm:justify-between sm:p-5">
        <div>
            <p className="text-xs text-[#6d7175] uppercase tracking-wide">{singular}</p>
            {/* "Application" on education, "Lead" on general */}
            <h1 className="text-lg font-semibold text-[#202223]">{lead.full_name}</h1>
        </div>
        <Button onClick={handleConvert}>
            Convert to {contactLabel.singular}   {/* "Convert to Student" */}
        </Button>
    </div>
</div>
```

### What changes between profiles — zero component code

| Profile | `useLabel('lead')` | `useLabel('deal')` | `useLabel('contact')` |
|---------|-------------------|-------------------|----------------------|
| general | Lead / Leads | Deal / Deals | Contact / Contacts |
| education | Application / Applications | Enrollment / Enrollments | Student / Students |
| realstate | Property Enquiry / Enquiries | Property Deal / Deals | Client / Clients |
| pharma | Doctor Lead / Doctor Leads | Product Order / Orders | Doctor / Doctors |
| garments | Buyer Enquiry / Enquiries | Order / Orders | Buyer / Buyers |

All Lead pages, Deal pages, sidebar items, breadcrumbs, stat card titles, empty states, and action buttons read from `useLabel()`. No `if (profile === 'education')` anywhere in component code.
