# @wisemen/wise-crm-web

A comprehensive CRM component library for Vue 3 applications, providing ready-to-use components, composables, and utilities for building CRM features.

## Table of Contents

- [Installation](#installation)
- [Quick Start](#quick-start)
- [Configuration](#configuration)
- [Components](#components)
- [Composables](#composables)
- [Routing](#routing)
- [Internationalization](#internationalization)
- [Icons](#icons)
- [Testing Utilities](#testing-utilities)
- [Type System](#type-system)

---

## Installation

```bash
# In a monorepo with pnpm workspaces
pnpm add @wisemen/wise-crm-web@workspace:*
```

Or for external projects:

```bash
npm install @wisemen/wise-crm-web
```

---

## Quick Start

### 1. Initialize the CRM

Initialize the CRM configuration in your root layout or app entry point:

```vue
<script setup lang="ts">
import { useWiseCrmConfig } from '@wisemen/wise-crm-web'
import { BoardColumnTransitionTriggerType } from '@wisemen/wise-crm-web/client'

const wiseCrm = useWiseCrmConfig()

wiseCrm.init({
  apiCrmBaseUrl: 'https://api.your-crm.com',
  getAccessToken: () => oAuthClient.getAccessToken(),
  googleMapsApiKey: 'YOUR_GOOGLE_MAPS_API_KEY',
  locale: 'en-US',
  options: {
    deals: {
      allowedTransitionTriggerTypes: [
        BoardColumnTransitionTriggerType.EMAIL,
        BoardColumnTransitionTriggerType.MANUAL,
      ],
    },
    widgets: {
      entityStatistics: {
        viewAllBusinessRouteName: 'crm-businesses',
        viewAllIndividualRouteName: 'crm-individuals',
      },
      recentlyCreatedEntities: true,
    },
  },
})
</script>
```

### 2. Set Up Routes

Configure CRM routes in your router:

```typescript
import { useWiseCrmRoutes } from '@wisemen/wise-crm-web'

const routes = [
  // ... your other routes
  useWiseCrmRoutes({
    businessTabRoutes: [
      {
        label: computed(() => 'Information'),
        route: {
          name: 'crm-business-information',
          component: BusinessInformationTabView,
        },
      },
    ],
    individualTabRoutes: [
      {
        label: computed(() => 'Information'),
        route: {
          name: 'crm-individual-information',
          component: IndividualInformationTabView,
        },
      },
    ],
    dealTabRoutes: [
      {
        label: computed(() => 'Information'),
        route: {
          name: 'crm-deal-information',
          component: DealInformationTabView,
        },
      },
    ],
    excludedReturnRoutes: [], // Optional: routes to exclude from return navigation
  }),
]
```

### 3. Integrate i18n and Icons

```typescript
// i18n setup
import { wiseCrmLocales } from '@wisemen/wise-crm-web'

const messages = {
  'en-US': {
    ...yourEnLocale,
    ...wiseCrmLocales['en-US'],
  },
  'nl-BE': {
    ...yourNlLocale,
    ...wiseCrmLocales['nl-BE'],
  },
}

// Icons setup
import { wiseCrmIcons } from '@wisemen/wise-crm-web'

export const icons = {
  ...wiseCrmIcons,
  ...yourCustomIcons,
}
```

---

## Configuration

### `useWiseCrmConfig().init(options)`

Initialize the CRM with the following configuration options:

#### Required Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `apiCrmBaseUrl` | `string` | Base URL for the CRM API |
| `getAccessToken` | `() => Promise<string>` | Function that returns the current authentication token |
| `googleMapsApiKey` | `string` | Google Maps API key for address autocomplete |
| `locale` | `Locale` | Initial locale ('en-US' or 'nl-BE') |

#### Options

##### `options.deals`

Configure deal/pipeline behavior:

```typescript
{
  deals: {
    // Types of transitions allowed in the deal board
    allowedTransitionTriggerTypes: BoardColumnTransitionTriggerType[]
  } | null
}
```

Available transition types:
- `BoardColumnTransitionTriggerType.MANUAL` - Manual drag & drop
- `BoardColumnTransitionTriggerType.EMAIL` - Email-triggered transitions

##### `options.widgets`

Configure widget behaviors:

```typescript
{
  widgets: {
    entityStatistics?: {
      // Route names for "View All" buttons in statistics widgets
      viewAllBusinessRouteName: string
      viewAllIndividualRouteName: string
    }
    // Enable recently created entities widget
    recentlyCreatedEntities?: true
  }
}
```

---

## Components

### Overview Components

#### `WiseCrmOverviewContent`

Main CRM dashboard with statistics widgets and recent activity.

```vue
<template>
  <WiseCrmOverviewContent />
</template>
```
### Deal Components

#### `WiseCrmCrmDealOverview`

Kanban-style deal pipeline board with drag-and-drop.

```vue
<template>
  <WiseCrmCrmDealOverview />
</template>
```

### Dialog Components

#### `WiseCrmContactCreateDialog`

Dialog for adding contact persons to a business.

```vue
<script setup lang="ts">
import { useWiseCrmContactCreateDialog, WiseCrmContactCreateDialog } from '@wisemen/wise-crm-web'

const contactDialog = useWiseCrmContactCreateDialog()

// Open from anywhere in your app
function addContact(): void {
  contactDialog.open(businessUuid)
}
</script>

<template>
  <WiseCrmContactCreateDialog />
</template>
```

### Card Components

#### `WiseCrmBusinessUpdateCard`

Form card for updating business information.

```vue
<script setup lang="ts">
import { useRouteParams } from '@vueuse/router'
import type { WiseCrmBusinessUuid } from '@wisemen/wise-crm-web'
import { WiseCrmBusinessUpdateCard } from '@wisemen/wise-crm-web'

const businessUuid = useRouteParams<WiseCrmBusinessUuid>('businessUuid', null)
</script>

<template>
  <WiseCrmBusinessUpdateCard
    v-if="businessUuid !== null"
    :business-uuid="businessUuid"
  />
</template>
```

**Props:**
- `businessUuid`: `WiseCrmBusinessUuid` - The business to update

#### `WiseCrmBusinessBillingInformationCard`

Form card for managing business billing information.

```vue
<template>
  <WiseCrmBusinessBillingInformationCard :business-uuid="businessUuid" />
</template>
```

**Props:**
- `businessUuid`: `WiseCrmBusinessUuid` - The business UUID

#### `WiseCrmIndividualUpdateCard`

Form card for updating individual information.

```vue
<template>
  <WiseCrmIndividualUpdateCard :individual-uuid="individualUuid" />
</template>
```

**Props:**
- `individualUuid`: `WiseCrmIndividualUuid` - The individual UUID

#### `WiseCrmIndividualBillingInformationCard`

Form card for managing individual billing information.

```vue
<template>
  <WiseCrmIndividualBillingInformationCard :individual-uuid="individualUuid" />
</template>
```

**Props:**
- `individualUuid`: `WiseCrmIndividualUuid` - The individual UUID

### Widget Components

#### `WiseCrmBusinessWidget`

Statistics widget showing business metrics.

```vue
<template>
  <WiseCrmBusinessWidget />
</template>
```

#### `WiseCrmIndividualWidget`

Statistics widget showing individual metrics.

```vue
<template>
  <WiseCrmIndividualWidget />
</template>
```

#### `WiseCrmRecentlyCreatedWidget`

Widget displaying recently created entities.

```vue
<template>
  <WiseCrmRecentlyCreatedWidget />
</template>
```

---

## Composables

### `useWiseCrm()`

Main composable for CRM operations and navigation.

```typescript
import { useWiseCrm } from '@wisemen/wise-crm-web'

const crm = useWiseCrm()
```

#### Methods

##### `openBusinessDetail(businessUuid: WiseCrmBusinessUuid): void`

Navigate to business detail page.

```typescript
crm.openBusinessDetail(businessUuid)
```

##### `openIndividualDetail(individualUuid: WiseCrmIndividualUuid): void`

Navigate to individual detail page.

```typescript
crm.openIndividualDetail(individualUuid)
```

##### `getEntityConfig(type: EntityType): EntityConfig | null`

Get configuration for a specific entity type.

```typescript
const businessConfig = crm.getEntityConfig(EntityType.BUSINESS)
console.log(businessConfig?.name) // Localized name like "Business" or "Bedrijf"
```

##### `goToReturnRoute(): void`

Navigate back to the stored return route (useful for "back" buttons).

```typescript
crm.goToReturnRoute()
```

##### `storeReturnRoute(route: RouteLocationNormalized, excludedRoutes?: (string | RegExp)[]): void`

Manually store a return route (usually handled automatically by `useWiseCrmRoutes`).

```typescript
crm.storeReturnRoute(router.currentRoute.value, ['crm-detail'])
```

### `useWiseCrmConfig()`

CRM initialization and configuration composable.

```typescript
import { useWiseCrmConfig } from '@wisemen/wise-crm-web'

const wiseCrm = useWiseCrmConfig()
```

#### Properties

##### `isInitialized: Ref<boolean>`

Reactive flag indicating if CRM has been initialized.

```typescript
watch(wiseCrm.isInitialized, (initialized) => {
  if (initialized) {
    console.log('CRM is ready!')
  }
})
```

#### Methods

##### `init(props: InitProps): Promise<void>`

Initialize the CRM (see [Configuration](#configuration) section).

##### `getConfig(): TenantConfig`

Get the current tenant configuration.

```typescript
const config = wiseCrm.getConfig()
console.log(config.entities) // Entity configurations
```

##### `getOptions(): InitProps['options']`

Get the initialization options.

```typescript
const options = wiseCrm.getOptions()
console.log(options.deals?.allowedTransitionTriggerTypes)
```

##### `getCustomFieldDefinitionForEntity(entityType: EntityType): CustomFieldDefinitionIndex[]`

Get custom field definitions for an entity type.

```typescript
const customFields = wiseCrm.getCustomFieldDefinitionForEntity(EntityType.BUSINESS)
```

### `useWiseCrmContactCreateDialog()`

Composable for controlling the contact creation dialog.

```typescript
import { useWiseCrmContactCreateDialog } from '@wisemen/wise-crm-web'

const contactDialog = useWiseCrmContactCreateDialog()
```

#### Methods

##### `open(businessUuid: WiseCrmBusinessUuid): void`

Open the contact creation dialog for a specific business.

```typescript
contactDialog.open(businessUuid)
```

---

## Routing

### `useWiseCrmRoutes(options)`

Generate CRM routes with tab-based detail views.

```typescript
import type { ComputedRef } from 'vue'
import { useWiseCrmRoutes } from '@wisemen/wise-crm-web'

interface CrmRouteOptions {
  label: ComputedRef<string>
  route: RouteRecordRaw
}

const crmRoutes = useWiseCrmRoutes({
  businessTabRoutes: CrmRouteOptions[],
  individualTabRoutes: CrmRouteOptions[],
  dealTabRoutes: CrmRouteOptions[],
  excludedReturnRoutes?: (string | RegExp)[], // Optional
})
```

#### Parameters

- `businessTabRoutes`: Array of tab routes for business detail pages
- `individualTabRoutes`: Array of tab routes for individual detail pages
- `dealTabRoutes`: Array of tab routes for deal detail pages
- `excludedReturnRoutes`: Routes to exclude from return navigation tracking

#### Example

```typescript
const crmRoutes = useWiseCrmRoutes({
  businessTabRoutes: [
    {
      label: computed(() => i18n.t('information')),
      route: {
        name: 'crm-business-information',
        path: 'information',
        component: BusinessInformationView,
      },
    },
    {
      label: computed(() => i18n.t('contacts')),
      route: {
        name: 'crm-business-contacts',
        path: 'contacts',
        component: BusinessContactsView,
      },
    },
  ],
  individualTabRoutes: [
    {
      label: computed(() => i18n.t('information')),
      route: {
        name: 'crm-individual-information',
        path: 'information',
        component: IndividualInformationView,
      },
    },
  ],
  dealTabRoutes: [],
  excludedReturnRoutes: [
    'crm-business-detail',
    'crm-individual-detail',
    /^crm-.*-edit$/,
  ],
})
```

### Generated Routes

The composable automatically generates these route patterns:

- `/individual/:individualUuid` - Individual detail page with tabs
- `/business/:businessUuid` - Business detail page with tabs
- `/deal/:dealUuid` - Deal detail page with tabs

---

## Internationalization

### Available Locales

- `en-US` - English (United States)
- `nl-BE` - Dutch (Belgium)

### Integration

```typescript
import { wiseCrmLocales } from '@wisemen/wise-crm-web'

const i18n = createI18n({
  messages: {
    'en-US': {
      ...yourMessages,
      ...wiseCrmLocales['en-US'],
    },
    'nl-BE': {
      ...yourMessages,
      ...wiseCrmLocales['nl-BE'],
    },
  },
})
```

### Updating Locale

The CRM automatically watches for locale changes if you're using vue-i18n. To manually update:

```typescript
import { useI18n } from 'vue-i18n'

const i18n = useI18n()
i18n.locale.value = 'nl-BE' // CRM will automatically update
```

---

## Icons

### Integration

```typescript
import { wiseCrmIcons } from '@wisemen/wise-crm-web'

export const icons = {
  ...wiseCrmIcons,
  // Your custom icons
  customIcon: () => import('./CustomIcon.vue'),
}
```

### Available Icons

The CRM includes icons for:
- Entity types (business, individual, deal)
- Actions (add, edit, delete, search)
- Navigation (back, forward, close)
- Status indicators
- And more...

---

## Testing Utilities

### Factory Classes

The library includes factory classes for generating mock data in tests.

#### Import Path

```typescript
import {
  BusinessFactory,
  IndividualFactory,
  DealFactory,
  NoteFactory,
} from '@wisemen/wise-crm-web/testing'
```

### `BusinessFactory`

#### Methods

##### `createDetail(overrides?): ViewBusinessDetailResponse`

Create a mock business detail object.

```typescript
const business = BusinessFactory.createDetail({
  name: 'Custom Corp',
  email: 'custom@example.com',
})
```

##### `createIndex(overrides?): ViewBusinessIndexResponseItem`

Create a mock business list item.

```typescript
const businessItem = BusinessFactory.createIndex({
  name: 'List Item Corp',
})
```

##### `createIndexList(count, overrides?): ViewBusinessIndexResponseItem[]`

Create an array of mock business list items.

```typescript
const businesses = BusinessFactory.createIndexList(10)
```

##### `createBillingInformation(overrides?): ViewBusinessBillingInformationDetailResponse`

Create mock billing information.

```typescript
const billing = BusinessFactory.createBillingInformation({
  invoiceEmail: 'billing@example.com',
})
```

##### `createContactPerson(overrides?): BusinessContactPersonResponse`

Create a mock contact person.

```typescript
const contact = BusinessFactory.createContactPerson({
  firstName: 'John',
  lastName: 'Doe',
})
```

##### `createStatistics(overrides?): ViewBusinessStatisticsResponse`

Create mock business statistics.

```typescript
const stats = BusinessFactory.createStatistics({
  total: 100,
  monthlyAdded: 10,
})
```

### `IndividualFactory`

Similar methods to `BusinessFactory` for individual entities:

- `createDetail(overrides?)`
- `createIndex(overrides?)`
- `createIndexList(count, overrides?)`
- `createBillingInformation(overrides?)`
- `createStatistics(overrides?)`

### `DealFactory`

- `createDetail(overrides?)`
- `createIndex(overrides?)`
- `createIndexList(count, overrides?)`

### `NoteFactory`

- `create(overrides?)`
- `createList(count, overrides?)`

### Testing Example

```typescript
import { expect, test } from '@playwright/test'
import { BusinessFactory } from '@wisemen/wise-crm-web/testing'
import { HttpResponse } from 'msw'

test('Create business', async ({ page, http, worker }) => {
  const mockBusiness = BusinessFactory.createDetail({
    name: 'Test Business',
  })

  await worker.use(
    http.post('*/api/v1/businesses', () => {
      return HttpResponse.json(
        { uuid: mockBusiness.uuid },
        { status: 201 }
      )
    }),
    http.get(`*/api/v1/businesses/${mockBusiness.uuid}`, () => {
      return HttpResponse.json(mockBusiness)
    }),
  )

  await page.goto('/crm/businesses')
  // ... rest of test
})
```

---

## Type System

### Entity UUID Types

The library exports branded UUID types for type safety:

```typescript
import type {
  WiseCrmBusinessUuid,
  WiseCrmIndividualUuid,
} from '@wisemen/wise-crm-web'

// These are branded types that prevent mixing UUIDs
function updateBusiness(uuid: WiseCrmBusinessUuid): void {
  // TypeScript ensures you can't pass WiseCrmIndividualUuid here
}
```

### Validation Schemas

UUID schemas for runtime validation:

```typescript
import {
  businessUuidSchema,
  individualUuidSchema,
} from '@wisemen/wise-crm-web'

// Use with zod or similar validators
const result = businessUuidSchema.safeParse(value)
```

### Dialog Content Tabs

```typescript
import type { WiseCrmDialogContentTab } from '@wisemen/wise-crm-web'

// Type for custom dialog tab content
const tabs: WiseCrmDialogContentTab[] = [
  { label: 'General', component: GeneralTab },
  { label: 'Advanced', component: AdvancedTab },
]
```

---

## Environment Variables

### Required

```env
# CRM API Base URL
API_CRM_BASE_URL=https://api.your-crm.com

# Google Maps API Key (for address autocomplete)
GOOGLE_MAPS_API_KEY=your_google_maps_api_key
```

### Example `.env`

```env
API_CRM_BASE_URL=https://wise-crm.development.appwi.se
GOOGLE_MAPS_API_KEY=AIzaSyD0DOeAK9X_gXAtwPEyKPl4CaJc1dhKC9Q
```

---

## Advanced Usage

### Custom Field Definitions

Access custom field definitions for dynamic form generation:

```typescript
import { EntityType } from '@wisemen/wise-crm-web/client'
import { useWiseCrmConfig } from '@wisemen/wise-crm-web'

const crmConfig = useWiseCrmConfig()

const customFields = crmConfig.getCustomFieldDefinitionForEntity(
  EntityType.BUSINESS
)

// Use customFields to render dynamic form fields
```

### Entity Configuration

Get localized entity names and colors:

```typescript
import { EntityType } from '@wisemen/wise-crm-web/client'
import { useWiseCrm } from '@wisemen/wise-crm-web'

const crm = useWiseCrm()
const businessConfig = crm.getEntityConfig(EntityType.BUSINESS)

console.log(businessConfig?.name) // "Business" or "Bedrijf"
console.log(businessConfig?.color) // "blue", "green", etc.
```

### CSS Custom Properties

The CRM automatically sets CSS custom properties for entity colors:

```css
/* Available CSS variables */
--wise-crm-business-50
--wise-crm-business-100
--wise-crm-business-200
/* ... etc for all color shades */

--wise-crm-individual-50
--wise-crm-individual-100
/* ... etc */
```

Use these in your custom components for consistent theming:

```vue
<style scoped>
.business-badge {
  background-color: var(--wise-crm-business-100);
  color: var(--wise-crm-business-700);
}
</style>
```

---

## Troubleshooting

### "CRM Tenant Config not initialized" Error

**Cause:** Attempting to use CRM components before initialization.

**Solution:** Ensure `useWiseCrmConfig().init()` is called before rendering any CRM components. Use `isInitialized` to conditionally render:

```vue
<script setup lang="ts">
import { useWiseCrmConfig } from '@wisemen/wise-crm-web'

const crmConfig = useWiseCrmConfig()

// Initialize in onMounted or similar
onMounted(async () => {
  await crmConfig.init({ /* ... */ })
})
</script>

<template>
  <div v-if="crmConfig.isInitialized.value">
    <WiseCrmOverviewContent />
  </div>
  <div v-else>
    Loading CRM...
  </div>
</template>
```

### TypeScript Errors with Factory Methods

**Cause:** Stale TypeScript cache or build artifacts.

**Solution:**
1. Restart TypeScript server: `Cmd+Shift+P` → "TypeScript: Restart TS Server"
2. Rebuild packages: `pnpm build`
3. Clear build cache: `rm -rf dist node_modules/.cache`

### Address Autocomplete Not Working

**Cause:** Invalid or missing Google Maps API key.

**Solution:** Ensure your `GOOGLE_MAPS_API_KEY` environment variable is set and the key has the Places API enabled in Google Cloud Console.

---

## Examples

### Complete Integration Example

```vue
<!-- App.vue -->
<script setup lang="ts">
import { useWiseCrmConfig } from '@wisemen/wise-crm-web'
import { BoardColumnTransitionTriggerType } from '@wisemen/wise-crm-web/client'
import { onMounted } from 'vue'

const crmConfig = useWiseCrmConfig()

onMounted(async () => {
  await crmConfig.init({
    apiCrmBaseUrl: import.meta.env.API_CRM_BASE_URL,
    getAccessToken: () => authClient.getToken(),
    googleMapsApiKey: import.meta.env.GOOGLE_MAPS_API_KEY,
    locale: 'en-US',
    options: {
      deals: {
        allowedTransitionTriggerTypes: [
          BoardColumnTransitionTriggerType.MANUAL,
        ],
      },
      widgets: {
        entityStatistics: {
          viewAllBusinessRouteName: 'businesses',
          viewAllIndividualRouteName: 'individuals',
        },
        recentlyCreatedEntities: true,
      },
    },
  })
})
</script>

<template>
  <RouterView v-if="crmConfig.isInitialized.value" />
</template>
```

```typescript
// router.ts
import { createRouter } from 'vue-router'
import { useWiseCrmRoutes } from '@wisemen/wise-crm-web'
import { computed } from 'vue'

const router = createRouter({
  routes: [
    {
      path: '/crm',
      children: [
        {
          name: 'crm-overview',
          path: '',
          component: () => import('./views/CrmOverview.vue'),
        },
        {
          name: 'crm-businesses',
          path: 'businesses',
          component: () => import('./views/BusinessList.vue'),
        },
        {
          name: 'crm-individuals',
          path: 'individuals',
          component: () => import('./views/IndividualList.vue'),
        },
      ],
    },
    useWiseCrmRoutes({
      businessTabRoutes: [
        {
          label: computed(() => 'Information'),
          route: {
            name: 'business-info',
            path: 'information',
            component: () => import('./views/BusinessInfo.vue'),
          },
        },
      ],
      individualTabRoutes: [
        {
          label: computed(() => 'Information'),
          route: {
            name: 'individual-info',
            path: 'information',
            component: () => import('./views/IndividualInfo.vue'),
          },
        },
      ],
      dealTabRoutes: [],
    }),
  ],
})
```

---

## License

MIT

## Support

For issues and questions, please contact the WiseMen Digital team.
