feat(frontend): Align documentation with development - complete frontend audit
- Add Sales/Commissions routes to router (11 new routes) - Complete authStore (refreshTokens, updateProfile methods) - Add JSDoc documentation to 40+ components across all categories - Create FRONTEND-ROUTING.md with complete route mapping - Create FRONTEND-PAGES-SPEC.md with 38 page specifications - Update FRONTEND_INVENTORY.yml to v4.1.0 with resolved gaps Components documented: ai/, audit/, commissions/, feature-flags/, notifications/, sales/, storage/, webhooks/, whatsapp/ Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
0b90d87c1f
commit
f853c49568
@ -17,6 +17,32 @@ interface AIChatProps {
|
|||||||
onUsageUpdate?: () => void;
|
onUsageUpdate?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI Chat component that provides an interactive chat interface with an AI assistant.
|
||||||
|
* Supports conversation history, model selection, and configurable system prompts.
|
||||||
|
*
|
||||||
|
* @description Interactive chat component for communicating with AI models via OpenRouter.
|
||||||
|
* Features auto-scrolling, message history, loading states, and customizable appearance.
|
||||||
|
*
|
||||||
|
* @param {string} [systemPrompt] - Custom system prompt to override the default AI behavior
|
||||||
|
* @param {string} [placeholder='Type your message...'] - Placeholder text for the input field
|
||||||
|
* @param {string} [className] - Additional CSS classes to apply to the container
|
||||||
|
* @param {boolean} [showModelSelector=false] - Whether to show the AI model selection dropdown
|
||||||
|
* @param {() => void} [onUsageUpdate] - Callback function triggered after each AI response for usage tracking
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Basic usage
|
||||||
|
* <AIChat />
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // With custom system prompt and model selector
|
||||||
|
* <AIChat
|
||||||
|
* systemPrompt="You are a helpful coding assistant."
|
||||||
|
* showModelSelector={true}
|
||||||
|
* onUsageUpdate={() => refetchUsage()}
|
||||||
|
* className="h-[600px]"
|
||||||
|
* />
|
||||||
|
*/
|
||||||
export function AIChat({
|
export function AIChat({
|
||||||
systemPrompt,
|
systemPrompt,
|
||||||
placeholder = 'Type your message...',
|
placeholder = 'Type your message...',
|
||||||
|
|||||||
@ -13,6 +13,25 @@ interface AIConfigForm {
|
|||||||
log_conversations: boolean;
|
log_conversations: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI Settings component for configuring AI integration at the organization level.
|
||||||
|
* Provides controls for enabling/disabling AI, selecting models, adjusting parameters,
|
||||||
|
* and viewing usage statistics.
|
||||||
|
*
|
||||||
|
* @description Administrative panel for managing AI configuration including model selection,
|
||||||
|
* temperature, max tokens, system prompts, and privacy settings. Displays real-time health
|
||||||
|
* status and usage metrics.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Basic usage in settings page
|
||||||
|
* <AISettings />
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Typically used within an admin or settings layout
|
||||||
|
* <SettingsLayout>
|
||||||
|
* <AISettings />
|
||||||
|
* </SettingsLayout>
|
||||||
|
*/
|
||||||
export function AISettings() {
|
export function AISettings() {
|
||||||
const { data: config, isLoading: configLoading } = useAIConfig();
|
const { data: config, isLoading: configLoading } = useAIConfig();
|
||||||
const { data: models } = useAIModels();
|
const { data: models } = useAIModels();
|
||||||
|
|||||||
@ -8,6 +8,42 @@ export interface ChatMessageProps {
|
|||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chat message component that renders a single message in the AI chat interface.
|
||||||
|
* Supports different visual styles for user, assistant, and system messages.
|
||||||
|
*
|
||||||
|
* @description Displays a chat message with role-specific styling, avatar icons,
|
||||||
|
* and optional timestamp. Handles loading states with animated dots for pending
|
||||||
|
* assistant responses. System messages are rendered as centered badges.
|
||||||
|
*
|
||||||
|
* @param {'user' | 'assistant' | 'system'} role - The role of the message sender
|
||||||
|
* @param {string} content - The text content of the message
|
||||||
|
* @param {Date} [timestamp] - Optional timestamp to display below the message
|
||||||
|
* @param {boolean} [isLoading=false] - Whether to show loading animation instead of content
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // User message
|
||||||
|
* <ChatMessage
|
||||||
|
* role="user"
|
||||||
|
* content="Hello, can you help me?"
|
||||||
|
* timestamp={new Date()}
|
||||||
|
* />
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Assistant message with loading state
|
||||||
|
* <ChatMessage
|
||||||
|
* role="assistant"
|
||||||
|
* content=""
|
||||||
|
* isLoading={true}
|
||||||
|
* />
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // System message
|
||||||
|
* <ChatMessage
|
||||||
|
* role="system"
|
||||||
|
* content="Conversation started"
|
||||||
|
* />
|
||||||
|
*/
|
||||||
export function ChatMessage({ role, content, timestamp, isLoading }: ChatMessageProps) {
|
export function ChatMessage({ role, content, timestamp, isLoading }: ChatMessageProps) {
|
||||||
const isUser = role === 'user';
|
const isUser = role === 'user';
|
||||||
const isSystem = role === 'system';
|
const isSystem = role === 'system';
|
||||||
|
|||||||
@ -15,8 +15,13 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the ActivityTimeline component.
|
||||||
|
*/
|
||||||
interface ActivityTimelineProps {
|
interface ActivityTimelineProps {
|
||||||
|
/** Array of activity logs to display in the timeline */
|
||||||
activities: ActivityLog[];
|
activities: ActivityLog[];
|
||||||
|
/** Whether the component is in a loading state */
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,6 +51,26 @@ const colorMap: Record<string, string> = {
|
|||||||
payment: 'bg-emerald-100 text-emerald-600 dark:bg-emerald-900/30 dark:text-emerald-400',
|
payment: 'bg-emerald-100 text-emerald-600 dark:bg-emerald-900/30 dark:text-emerald-400',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a vertical timeline of user activity events.
|
||||||
|
*
|
||||||
|
* @description Renders a chronological list of activity logs with type-specific icons
|
||||||
|
* and color coding. Each activity shows the type label, description, user name,
|
||||||
|
* resource type, relative timestamp, and any associated metadata. Supports loading
|
||||||
|
* state with animated skeleton placeholders.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.activities - Array of activity logs to display in the timeline
|
||||||
|
* @param props.isLoading - Whether to show loading skeleton instead of data
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* <ActivityTimeline
|
||||||
|
* activities={recentActivities}
|
||||||
|
* isLoading={isLoadingActivities}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function ActivityTimeline({ activities, isLoading }: ActivityTimelineProps) {
|
export function ActivityTimeline({ activities, isLoading }: ActivityTimelineProps) {
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -4,9 +4,15 @@ import { getAuditActionLabel } from '@/hooks/useAudit';
|
|||||||
import { Filter, X, Search } from 'lucide-react';
|
import { Filter, X, Search } from 'lucide-react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the AuditFilters component.
|
||||||
|
*/
|
||||||
interface AuditFiltersProps {
|
interface AuditFiltersProps {
|
||||||
|
/** Current filter parameters applied to the audit log query */
|
||||||
filters: QueryAuditLogsParams;
|
filters: QueryAuditLogsParams;
|
||||||
|
/** Callback invoked when any filter value changes */
|
||||||
onFiltersChange: (filters: QueryAuditLogsParams) => void;
|
onFiltersChange: (filters: QueryAuditLogsParams) => void;
|
||||||
|
/** List of available entity types for the entity type filter dropdown */
|
||||||
entityTypes?: string[];
|
entityTypes?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,6 +27,28 @@ const AUDIT_ACTIONS: AuditAction[] = [
|
|||||||
'import',
|
'import',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a collapsible filter panel for audit log queries.
|
||||||
|
*
|
||||||
|
* @description Renders a filter interface with controls for action type, entity type,
|
||||||
|
* and date range (from/to). Supports expanding/collapsing the filter panel, displays
|
||||||
|
* active filter count as a badge, and shows removable filter badges when collapsed.
|
||||||
|
* Automatically resets pagination to page 1 when filters change.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.filters - Current filter parameters applied to the audit log query
|
||||||
|
* @param props.onFiltersChange - Callback invoked when any filter value changes
|
||||||
|
* @param props.entityTypes - List of available entity types for the dropdown
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* <AuditFilters
|
||||||
|
* filters={queryParams}
|
||||||
|
* onFiltersChange={setQueryParams}
|
||||||
|
* entityTypes={['user', 'tenant', 'subscription']}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function AuditFilters({ filters, onFiltersChange, entityTypes = [] }: AuditFiltersProps) {
|
export function AuditFilters({ filters, onFiltersChange, entityTypes = [] }: AuditFiltersProps) {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
@ -181,6 +209,16 @@ export function AuditFilters({ filters, onFiltersChange, entityTypes = [] }: Aud
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a removable badge for an active filter.
|
||||||
|
*
|
||||||
|
* @description Internal component that renders a pill-shaped badge showing
|
||||||
|
* the filter label with a close button to remove the filter.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.label - Text to display in the badge
|
||||||
|
* @param props.onRemove - Callback invoked when the remove button is clicked
|
||||||
|
*/
|
||||||
function FilterBadge({ label, onRemove }: { label: string; onRemove: () => void }) {
|
function FilterBadge({ label, onRemove }: { label: string; onRemove: () => void }) {
|
||||||
return (
|
return (
|
||||||
<span className="inline-flex items-center gap-1 px-2 py-1 bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-400 rounded-full text-sm">
|
<span className="inline-flex items-center gap-1 px-2 py-1 bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-400 rounded-full text-sm">
|
||||||
|
|||||||
@ -4,11 +4,35 @@ import { getAuditActionLabel, getAuditActionColor } from '@/hooks/useAudit';
|
|||||||
import { ChevronDown, ChevronRight, User, Clock, Globe, Code } from 'lucide-react';
|
import { ChevronDown, ChevronRight, User, Clock, Globe, Code } from 'lucide-react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the AuditLogRow component.
|
||||||
|
*/
|
||||||
interface AuditLogRowProps {
|
interface AuditLogRowProps {
|
||||||
|
/** The audit log entry to display */
|
||||||
log: AuditLog;
|
log: AuditLog;
|
||||||
|
/** Optional callback invoked when the user clicks to view full details */
|
||||||
onSelect?: (log: AuditLog) => void;
|
onSelect?: (log: AuditLog) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a single audit log entry as an expandable row.
|
||||||
|
*
|
||||||
|
* @description Renders an audit log with summary information (action, entity, user, timestamp)
|
||||||
|
* in a collapsed state. When expanded, shows detailed information including old/new values,
|
||||||
|
* changed fields, metadata, IP address, endpoint, and response status.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.log - The audit log entry to display
|
||||||
|
* @param props.onSelect - Optional callback invoked when the user clicks to view full details
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* <AuditLogRow
|
||||||
|
* log={auditLogEntry}
|
||||||
|
* onSelect={(log) => openDetailModal(log)}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function AuditLogRow({ log, onSelect }: AuditLogRowProps) {
|
export function AuditLogRow({ log, onSelect }: AuditLogRowProps) {
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
|
||||||
|
|||||||
@ -3,11 +3,35 @@ import { getAuditActionLabel, getAuditActionColor } from '@/hooks/useAudit';
|
|||||||
import { Activity, TrendingUp, Users, FileText } from 'lucide-react';
|
import { Activity, TrendingUp, Users, FileText } from 'lucide-react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the AuditStatsCard component.
|
||||||
|
*/
|
||||||
interface AuditStatsCardProps {
|
interface AuditStatsCardProps {
|
||||||
|
/** Aggregated audit statistics including totals, breakdowns by action/entity, and daily trends */
|
||||||
stats: AuditStats;
|
stats: AuditStats;
|
||||||
|
/** Whether the component is in a loading state */
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays an overview card with audit statistics and trends.
|
||||||
|
*
|
||||||
|
* @description Renders a dashboard card showing key audit metrics including total logs,
|
||||||
|
* number of unique actions, entity types, and active users. Also displays breakdowns
|
||||||
|
* by action type and entity type, plus a daily activity trend chart for the last 7 days.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.stats - Aggregated audit statistics object
|
||||||
|
* @param props.isLoading - Whether to show loading skeleton instead of data
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* <AuditStatsCard
|
||||||
|
* stats={auditStats}
|
||||||
|
* isLoading={isLoadingStats}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function AuditStatsCard({ stats, isLoading }: AuditStatsCardProps) {
|
export function AuditStatsCard({ stats, isLoading }: AuditStatsCardProps) {
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,12 +1,51 @@
|
|||||||
import { DollarSign, Users, Calendar, TrendingUp, Clock, CheckCircle } from 'lucide-react';
|
import { DollarSign, Users, Calendar, TrendingUp, Clock, CheckCircle } from 'lucide-react';
|
||||||
import { DashboardSummary, TopEarner, PeriodEarnings } from '../../services/commissions/dashboard.api';
|
import { DashboardSummary, TopEarner, PeriodEarnings } from '../../services/commissions/dashboard.api';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the CommissionsDashboard component.
|
||||||
|
*/
|
||||||
interface CommissionsDashboardProps {
|
interface CommissionsDashboardProps {
|
||||||
|
/** Summary data containing totals for pending, approved, and paid commissions */
|
||||||
summary: DashboardSummary;
|
summary: DashboardSummary;
|
||||||
|
/** List of top earning users with their rankings and total earned amounts */
|
||||||
topEarners: TopEarner[];
|
topEarners: TopEarner[];
|
||||||
|
/** List of recent commission periods with their earnings data */
|
||||||
periodEarnings: PeriodEarnings[];
|
periodEarnings: PeriodEarnings[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a comprehensive dashboard for commission management.
|
||||||
|
* Shows summary cards with pending, approved, paid commissions and active schemes,
|
||||||
|
* along with top earners ranking and recent period performance.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.summary - Aggregated commission statistics including totals and counts
|
||||||
|
* @param props.topEarners - Ranked list of users by earnings
|
||||||
|
* @param props.periodEarnings - Historical period data with amounts and statuses
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* <CommissionsDashboard
|
||||||
|
* summary={{
|
||||||
|
* total_pending: 5000,
|
||||||
|
* pending_count: 10,
|
||||||
|
* total_approved: 15000,
|
||||||
|
* approved_count: 25,
|
||||||
|
* total_paid: 50000,
|
||||||
|
* paid_count: 100,
|
||||||
|
* active_schemes: 5,
|
||||||
|
* active_assignments: 20,
|
||||||
|
* currency: 'USD'
|
||||||
|
* }}
|
||||||
|
* topEarners={[
|
||||||
|
* { user_id: '1', user_name: 'John Doe', total_earned: 10000, entries_count: 50, rank: 1 }
|
||||||
|
* ]}
|
||||||
|
* periodEarnings={[
|
||||||
|
* { period_id: '1', period_name: 'January 2026', starts_at: '2026-01-01', ends_at: '2026-01-31', total_amount: 25000, status: 'paid' }
|
||||||
|
* ]}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function CommissionsDashboard({ summary, topEarners, periodEarnings }: CommissionsDashboardProps) {
|
export function CommissionsDashboard({ summary, topEarners, periodEarnings }: CommissionsDashboardProps) {
|
||||||
const formatCurrency = (amount: number) =>
|
const formatCurrency = (amount: number) =>
|
||||||
new Intl.NumberFormat('en-US', { style: 'currency', currency: summary.currency }).format(amount);
|
new Intl.NumberFormat('en-US', { style: 'currency', currency: summary.currency }).format(amount);
|
||||||
|
|||||||
@ -1,13 +1,45 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the EarningsCard component.
|
||||||
|
*/
|
||||||
interface EarningsCardProps {
|
interface EarningsCardProps {
|
||||||
|
/** Title displayed above the value (e.g., "Total Earnings") */
|
||||||
title: string;
|
title: string;
|
||||||
|
/** Main value to display (e.g., "$1,500.00") */
|
||||||
value: string;
|
value: string;
|
||||||
|
/** Icon element to display on the right side of the card */
|
||||||
icon: ReactNode;
|
icon: ReactNode;
|
||||||
|
/** Color theme for the card styling */
|
||||||
color: 'blue' | 'green' | 'yellow' | 'purple' | 'red';
|
color: 'blue' | 'green' | 'yellow' | 'purple' | 'red';
|
||||||
|
/** Optional subtitle displayed below the value */
|
||||||
subtitle?: string;
|
subtitle?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a styled card for showing earnings or commission statistics.
|
||||||
|
* Features a title, large value display, colored icon, and optional subtitle.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.title - Label describing the statistic
|
||||||
|
* @param props.value - Formatted value to display prominently
|
||||||
|
* @param props.icon - React node for the icon (e.g., from lucide-react)
|
||||||
|
* @param props.color - Color theme affecting the value text and icon background
|
||||||
|
* @param props.subtitle - Additional context displayed below the value
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* import { DollarSign } from 'lucide-react';
|
||||||
|
*
|
||||||
|
* <EarningsCard
|
||||||
|
* title="Total Earnings"
|
||||||
|
* value="$12,500.00"
|
||||||
|
* icon={<DollarSign className="h-6 w-6" />}
|
||||||
|
* color="green"
|
||||||
|
* subtitle="Last 30 days"
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function EarningsCard({ title, value, icon, color, subtitle }: EarningsCardProps) {
|
export function EarningsCard({ title, value, icon, color, subtitle }: EarningsCardProps) {
|
||||||
const colorClasses = {
|
const colorClasses = {
|
||||||
blue: 'bg-blue-100 text-blue-600',
|
blue: 'bg-blue-100 text-blue-600',
|
||||||
|
|||||||
@ -2,17 +2,60 @@ import { ChevronLeft, ChevronRight } from 'lucide-react';
|
|||||||
import { CommissionEntry } from '../../services/commissions/entries.api';
|
import { CommissionEntry } from '../../services/commissions/entries.api';
|
||||||
import { EntryStatusBadge } from './EntryStatusBadge';
|
import { EntryStatusBadge } from './EntryStatusBadge';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the EntriesList component.
|
||||||
|
*/
|
||||||
interface EntriesListProps {
|
interface EntriesListProps {
|
||||||
|
/** Array of commission entries to display in the table */
|
||||||
entries: CommissionEntry[];
|
entries: CommissionEntry[];
|
||||||
|
/** Total number of entries across all pages */
|
||||||
total: number;
|
total: number;
|
||||||
|
/** Current page number (1-indexed) */
|
||||||
page: number;
|
page: number;
|
||||||
|
/** Total number of pages available */
|
||||||
totalPages: number;
|
totalPages: number;
|
||||||
|
/** Array of selected entry IDs for bulk operations */
|
||||||
selectedEntries: string[];
|
selectedEntries: string[];
|
||||||
|
/** Callback when selection changes (for bulk approve/reject) */
|
||||||
onSelectionChange: (ids: string[]) => void;
|
onSelectionChange: (ids: string[]) => void;
|
||||||
|
/** Callback when page changes for pagination */
|
||||||
onPageChange: (page: number) => void;
|
onPageChange: (page: number) => void;
|
||||||
|
/** Callback to refresh the entries list */
|
||||||
onRefresh: () => void;
|
onRefresh: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a paginated table of commission entries with selection support.
|
||||||
|
* Allows selecting pending entries for bulk approval/rejection operations.
|
||||||
|
* Shows entry details including date, user, reference, amounts, and status.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.entries - Commission entries to display
|
||||||
|
* @param props.total - Total entry count for pagination info
|
||||||
|
* @param props.page - Current page number
|
||||||
|
* @param props.totalPages - Total pages for pagination controls
|
||||||
|
* @param props.selectedEntries - Currently selected entry IDs
|
||||||
|
* @param props.onSelectionChange - Handler for selection updates
|
||||||
|
* @param props.onPageChange - Handler for page navigation
|
||||||
|
* @param props.onRefresh - Handler to refresh data after operations
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* const [selected, setSelected] = useState<string[]>([]);
|
||||||
|
* const [page, setPage] = useState(1);
|
||||||
|
*
|
||||||
|
* <EntriesList
|
||||||
|
* entries={commissionEntries}
|
||||||
|
* total={100}
|
||||||
|
* page={page}
|
||||||
|
* totalPages={10}
|
||||||
|
* selectedEntries={selected}
|
||||||
|
* onSelectionChange={setSelected}
|
||||||
|
* onPageChange={setPage}
|
||||||
|
* onRefresh={() => refetch()}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function EntriesList({
|
export function EntriesList({
|
||||||
entries,
|
entries,
|
||||||
total,
|
total,
|
||||||
|
|||||||
@ -1,9 +1,32 @@
|
|||||||
import { EntryStatus } from '../../services/commissions/entries.api';
|
import { EntryStatus } from '../../services/commissions/entries.api';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the EntryStatusBadge component.
|
||||||
|
*/
|
||||||
interface EntryStatusBadgeProps {
|
interface EntryStatusBadgeProps {
|
||||||
|
/** The status of the commission entry */
|
||||||
status: EntryStatus;
|
status: EntryStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a colored badge indicating the status of a commission entry.
|
||||||
|
* Each status has a distinct color for quick visual identification:
|
||||||
|
* - pending: yellow
|
||||||
|
* - approved: green
|
||||||
|
* - rejected: red
|
||||||
|
* - paid: blue
|
||||||
|
* - cancelled: gray
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.status - The entry status to display
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* <EntryStatusBadge status="pending" />
|
||||||
|
* <EntryStatusBadge status="approved" />
|
||||||
|
* <EntryStatusBadge status="paid" />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function EntryStatusBadge({ status }: EntryStatusBadgeProps) {
|
export function EntryStatusBadge({ status }: EntryStatusBadgeProps) {
|
||||||
const getStatusConfig = (status: EntryStatus) => {
|
const getStatusConfig = (status: EntryStatus) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
|
|||||||
@ -2,15 +2,53 @@ import { ChevronLeft, ChevronRight, Lock, Unlock, DollarSign, Clock, CheckCircle
|
|||||||
import { CommissionPeriod, PeriodStatus } from '../../services/commissions/periods.api';
|
import { CommissionPeriod, PeriodStatus } from '../../services/commissions/periods.api';
|
||||||
import { useClosePeriod, useReopenPeriod, useMarkPeriodPaid } from '../../hooks/commissions';
|
import { useClosePeriod, useReopenPeriod, useMarkPeriodPaid } from '../../hooks/commissions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the PeriodManager component.
|
||||||
|
*/
|
||||||
interface PeriodManagerProps {
|
interface PeriodManagerProps {
|
||||||
|
/** Array of commission periods to display */
|
||||||
periods: CommissionPeriod[];
|
periods: CommissionPeriod[];
|
||||||
|
/** Total number of periods across all pages */
|
||||||
total: number;
|
total: number;
|
||||||
|
/** Current page number (1-indexed) */
|
||||||
page: number;
|
page: number;
|
||||||
|
/** Total number of pages available */
|
||||||
totalPages: number;
|
totalPages: number;
|
||||||
|
/** Callback when page changes for pagination */
|
||||||
onPageChange: (page: number) => void;
|
onPageChange: (page: number) => void;
|
||||||
|
/** Callback to refresh the periods list after operations */
|
||||||
onRefresh: () => void;
|
onRefresh: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages commission periods with lifecycle operations.
|
||||||
|
* Displays periods in a table with their date ranges, entry counts, and totals.
|
||||||
|
* Provides actions to close, reopen, and mark periods as paid based on status.
|
||||||
|
*
|
||||||
|
* Period lifecycle: open -> closed -> processing -> paid
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.periods - Commission periods to display
|
||||||
|
* @param props.total - Total period count for pagination info
|
||||||
|
* @param props.page - Current page number
|
||||||
|
* @param props.totalPages - Total pages for pagination controls
|
||||||
|
* @param props.onPageChange - Handler for page navigation
|
||||||
|
* @param props.onRefresh - Handler to refresh data after operations
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* const [page, setPage] = useState(1);
|
||||||
|
*
|
||||||
|
* <PeriodManager
|
||||||
|
* periods={commissionPeriods}
|
||||||
|
* total={24}
|
||||||
|
* page={page}
|
||||||
|
* totalPages={3}
|
||||||
|
* onPageChange={setPage}
|
||||||
|
* onRefresh={() => refetch()}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function PeriodManager({
|
export function PeriodManager({
|
||||||
periods,
|
periods,
|
||||||
total,
|
total,
|
||||||
|
|||||||
@ -3,12 +3,50 @@ import { X, Plus, Trash2 } from 'lucide-react';
|
|||||||
import { useCreateScheme, useUpdateScheme } from '../../hooks/commissions';
|
import { useCreateScheme, useUpdateScheme } from '../../hooks/commissions';
|
||||||
import { CreateSchemeDto, SchemeType, AppliesTo, TierConfig, CommissionScheme } from '../../services/commissions/schemes.api';
|
import { CreateSchemeDto, SchemeType, AppliesTo, TierConfig, CommissionScheme } from '../../services/commissions/schemes.api';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the SchemeForm component.
|
||||||
|
*/
|
||||||
interface SchemeFormProps {
|
interface SchemeFormProps {
|
||||||
|
/** Existing scheme to edit, or undefined for creating a new scheme */
|
||||||
scheme?: CommissionScheme;
|
scheme?: CommissionScheme;
|
||||||
|
/** Callback when the form is closed/cancelled */
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
/** Callback when the scheme is successfully created/updated */
|
||||||
onSuccess: () => void;
|
onSuccess: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal form for creating or editing commission schemes.
|
||||||
|
* Supports three scheme types: percentage, fixed amount, and tiered.
|
||||||
|
* Includes configuration for rate, amounts, tiers, application scope, and limits.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.scheme - Existing scheme for editing (omit for create mode)
|
||||||
|
* @param props.onClose - Handler to close the modal
|
||||||
|
* @param props.onSuccess - Handler called after successful save
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* // Create mode
|
||||||
|
* <SchemeForm
|
||||||
|
* onClose={() => setShowForm(false)}
|
||||||
|
* onSuccess={() => {
|
||||||
|
* setShowForm(false);
|
||||||
|
* refetch();
|
||||||
|
* }}
|
||||||
|
* />
|
||||||
|
*
|
||||||
|
* // Edit mode
|
||||||
|
* <SchemeForm
|
||||||
|
* scheme={existingScheme}
|
||||||
|
* onClose={() => setShowForm(false)}
|
||||||
|
* onSuccess={() => {
|
||||||
|
* setShowForm(false);
|
||||||
|
* refetch();
|
||||||
|
* }}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function SchemeForm({ scheme, onClose, onSuccess }: SchemeFormProps) {
|
export function SchemeForm({ scheme, onClose, onSuccess }: SchemeFormProps) {
|
||||||
const isEditing = !!scheme;
|
const isEditing = !!scheme;
|
||||||
const createMutation = useCreateScheme();
|
const createMutation = useCreateScheme();
|
||||||
|
|||||||
@ -2,15 +2,51 @@ import { Pencil, Trash2, Copy, ToggleLeft, ToggleRight, ChevronLeft, ChevronRigh
|
|||||||
import { CommissionScheme } from '../../services/commissions/schemes.api';
|
import { CommissionScheme } from '../../services/commissions/schemes.api';
|
||||||
import { useDeleteScheme, useDuplicateScheme, useToggleSchemeActive } from '../../hooks/commissions';
|
import { useDeleteScheme, useDuplicateScheme, useToggleSchemeActive } from '../../hooks/commissions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the SchemesList component.
|
||||||
|
*/
|
||||||
interface SchemesListProps {
|
interface SchemesListProps {
|
||||||
|
/** Array of commission schemes to display in the table */
|
||||||
schemes: CommissionScheme[];
|
schemes: CommissionScheme[];
|
||||||
|
/** Total number of schemes across all pages */
|
||||||
total: number;
|
total: number;
|
||||||
|
/** Current page number (1-indexed) */
|
||||||
page: number;
|
page: number;
|
||||||
|
/** Total number of pages available */
|
||||||
totalPages: number;
|
totalPages: number;
|
||||||
|
/** Callback when page changes for pagination */
|
||||||
onPageChange: (page: number) => void;
|
onPageChange: (page: number) => void;
|
||||||
|
/** Callback to refresh the schemes list after operations */
|
||||||
onRefresh: () => void;
|
onRefresh: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a paginated table of commission schemes with management actions.
|
||||||
|
* Shows scheme details including name, type, rate/amount, application scope, and status.
|
||||||
|
* Provides actions to toggle active status, duplicate, and delete schemes.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.schemes - Commission schemes to display
|
||||||
|
* @param props.total - Total scheme count for pagination info
|
||||||
|
* @param props.page - Current page number
|
||||||
|
* @param props.totalPages - Total pages for pagination controls
|
||||||
|
* @param props.onPageChange - Handler for page navigation
|
||||||
|
* @param props.onRefresh - Handler to refresh data after operations
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* const [page, setPage] = useState(1);
|
||||||
|
*
|
||||||
|
* <SchemesList
|
||||||
|
* schemes={commissionSchemes}
|
||||||
|
* total={15}
|
||||||
|
* page={page}
|
||||||
|
* totalPages={2}
|
||||||
|
* onPageChange={setPage}
|
||||||
|
* onRefresh={() => refetch()}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function SchemesList({
|
export function SchemesList({
|
||||||
schemes,
|
schemes,
|
||||||
total,
|
total,
|
||||||
|
|||||||
@ -9,14 +9,59 @@ import { MoreVertical, Pencil, Trash2, ToggleLeft, ToggleRight, Copy } from 'luc
|
|||||||
import { useState, useRef, useEffect } from 'react';
|
import { useState, useRef, useEffect } from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the FeatureFlagCard component.
|
||||||
|
*/
|
||||||
interface FeatureFlagCardProps {
|
interface FeatureFlagCardProps {
|
||||||
|
/** The feature flag data to display */
|
||||||
flag: FeatureFlag;
|
flag: FeatureFlag;
|
||||||
|
/** Callback invoked when the edit action is triggered */
|
||||||
onEdit: (flag: FeatureFlag) => void;
|
onEdit: (flag: FeatureFlag) => void;
|
||||||
|
/** Callback invoked when the delete action is triggered */
|
||||||
onDelete: (flag: FeatureFlag) => void;
|
onDelete: (flag: FeatureFlag) => void;
|
||||||
|
/** Callback invoked when the toggle action is triggered */
|
||||||
onToggle: (flag: FeatureFlag) => void;
|
onToggle: (flag: FeatureFlag) => void;
|
||||||
|
/** Indicates if the toggle operation is in progress */
|
||||||
isToggling?: boolean;
|
isToggling?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a feature flag as a card with its details, status, and action menu.
|
||||||
|
*
|
||||||
|
* @description Renders a card showing the feature flag's name, key, description,
|
||||||
|
* type, scope, category, rollout percentage, and default value. Includes actions
|
||||||
|
* for toggling, editing, and deleting the flag, plus a copy-to-clipboard button
|
||||||
|
* for the flag key.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.flag - The feature flag data to display
|
||||||
|
* @param props.onEdit - Callback invoked when the edit action is triggered
|
||||||
|
* @param props.onDelete - Callback invoked when the delete action is triggered
|
||||||
|
* @param props.onToggle - Callback invoked when the toggle action is triggered
|
||||||
|
* @param props.isToggling - Indicates if the toggle operation is in progress
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* <FeatureFlagCard
|
||||||
|
* flag={{
|
||||||
|
* id: '1',
|
||||||
|
* key: 'enable_dark_mode',
|
||||||
|
* name: 'Enable Dark Mode',
|
||||||
|
* description: 'Allows users to switch to dark theme',
|
||||||
|
* flag_type: 'boolean',
|
||||||
|
* scope: 'user',
|
||||||
|
* is_enabled: true,
|
||||||
|
* default_value: false,
|
||||||
|
* rollout_percentage: 100,
|
||||||
|
* category: 'UI',
|
||||||
|
* }}
|
||||||
|
* onEdit={(flag) => console.log('Edit', flag)}
|
||||||
|
* onDelete={(flag) => console.log('Delete', flag)}
|
||||||
|
* onToggle={(flag) => console.log('Toggle', flag)}
|
||||||
|
* isToggling={false}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function FeatureFlagCard({
|
export function FeatureFlagCard({
|
||||||
flag,
|
flag,
|
||||||
onEdit,
|
onEdit,
|
||||||
|
|||||||
@ -2,13 +2,21 @@ import { useState, useEffect } from 'react';
|
|||||||
import { FeatureFlag, FlagType, FlagScope, CreateFlagRequest, UpdateFlagRequest } from '@/services/api';
|
import { FeatureFlag, FlagType, FlagScope, CreateFlagRequest, UpdateFlagRequest } from '@/services/api';
|
||||||
import { Loader2 } from 'lucide-react';
|
import { Loader2 } from 'lucide-react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the FeatureFlagForm component.
|
||||||
|
*/
|
||||||
interface FeatureFlagFormProps {
|
interface FeatureFlagFormProps {
|
||||||
|
/** Existing flag data for editing mode; omit for create mode */
|
||||||
flag?: FeatureFlag;
|
flag?: FeatureFlag;
|
||||||
|
/** Callback invoked on form submission with the flag data */
|
||||||
onSubmit: (data: CreateFlagRequest | UpdateFlagRequest) => void;
|
onSubmit: (data: CreateFlagRequest | UpdateFlagRequest) => void;
|
||||||
|
/** Callback invoked when the cancel button is clicked */
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
|
/** Indicates if the submit operation is in progress */
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Available flag types for selection. */
|
||||||
const FLAG_TYPES: { value: FlagType; label: string }[] = [
|
const FLAG_TYPES: { value: FlagType; label: string }[] = [
|
||||||
{ value: 'boolean', label: 'Boolean' },
|
{ value: 'boolean', label: 'Boolean' },
|
||||||
{ value: 'string', label: 'String' },
|
{ value: 'string', label: 'String' },
|
||||||
@ -16,6 +24,7 @@ const FLAG_TYPES: { value: FlagType; label: string }[] = [
|
|||||||
{ value: 'json', label: 'JSON' },
|
{ value: 'json', label: 'JSON' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/** Available flag scopes for selection. */
|
||||||
const FLAG_SCOPES: { value: FlagScope; label: string; description: string }[] = [
|
const FLAG_SCOPES: { value: FlagScope; label: string; description: string }[] = [
|
||||||
{ value: 'global', label: 'Global', description: 'Applies to all tenants and users' },
|
{ value: 'global', label: 'Global', description: 'Applies to all tenants and users' },
|
||||||
{ value: 'tenant', label: 'Tenant', description: 'Can be overridden per tenant' },
|
{ value: 'tenant', label: 'Tenant', description: 'Can be overridden per tenant' },
|
||||||
@ -23,6 +32,38 @@ const FLAG_SCOPES: { value: FlagScope; label: string; description: string }[] =
|
|||||||
{ value: 'plan', label: 'Plan', description: 'Based on subscription plan' },
|
{ value: 'plan', label: 'Plan', description: 'Based on subscription plan' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Form for creating or editing a feature flag.
|
||||||
|
*
|
||||||
|
* @description Renders a form with fields for all feature flag properties including
|
||||||
|
* key (create only), name, description, type, scope, default value, category,
|
||||||
|
* rollout percentage, and enabled status. Validates input and parses the default
|
||||||
|
* value based on the selected flag type.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.flag - Existing flag data for editing mode; omit for create mode
|
||||||
|
* @param props.onSubmit - Callback invoked on form submission with the flag data
|
||||||
|
* @param props.onCancel - Callback invoked when the cancel button is clicked
|
||||||
|
* @param props.isLoading - Indicates if the submit operation is in progress
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* // Create mode
|
||||||
|
* <FeatureFlagForm
|
||||||
|
* onSubmit={(data) => createFlag(data)}
|
||||||
|
* onCancel={() => closeModal()}
|
||||||
|
* isLoading={isCreating}
|
||||||
|
* />
|
||||||
|
*
|
||||||
|
* // Edit mode
|
||||||
|
* <FeatureFlagForm
|
||||||
|
* flag={existingFlag}
|
||||||
|
* onSubmit={(data) => updateFlag(existingFlag.id, data)}
|
||||||
|
* onCancel={() => closeModal()}
|
||||||
|
* isLoading={isUpdating}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function FeatureFlagForm({ flag, onSubmit, onCancel, isLoading }: FeatureFlagFormProps) {
|
export function FeatureFlagForm({ flag, onSubmit, onCancel, isLoading }: FeatureFlagFormProps) {
|
||||||
const isEditing = !!flag;
|
const isEditing = !!flag;
|
||||||
|
|
||||||
|
|||||||
@ -3,14 +3,53 @@ import { FeatureFlag, TenantFlag } from '@/services/api';
|
|||||||
import { Plus, Trash2, ToggleLeft, ToggleRight, AlertCircle } from 'lucide-react';
|
import { Plus, Trash2, ToggleLeft, ToggleRight, AlertCircle } from 'lucide-react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the TenantOverridesPanel component.
|
||||||
|
*/
|
||||||
interface TenantOverridesPanelProps {
|
interface TenantOverridesPanelProps {
|
||||||
|
/** List of all available feature flags */
|
||||||
flags: FeatureFlag[];
|
flags: FeatureFlag[];
|
||||||
|
/** List of current tenant-specific flag overrides */
|
||||||
overrides: TenantFlag[];
|
overrides: TenantFlag[];
|
||||||
|
/** Callback invoked when adding a new override */
|
||||||
onAdd: (flagId: string, isEnabled: boolean, value?: any) => void;
|
onAdd: (flagId: string, isEnabled: boolean, value?: any) => void;
|
||||||
|
/** Callback invoked when removing an existing override */
|
||||||
onRemove: (flagId: string) => void;
|
onRemove: (flagId: string) => void;
|
||||||
|
/** Indicates if an operation is in progress */
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Panel for managing tenant-specific feature flag overrides.
|
||||||
|
*
|
||||||
|
* @description Displays a list of tenant overrides with the ability to add new
|
||||||
|
* overrides and remove existing ones. Allows tenants to customize global feature
|
||||||
|
* flag settings for their organization. Only flags that don't already have an
|
||||||
|
* override are available for selection.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.flags - List of all available feature flags
|
||||||
|
* @param props.overrides - List of current tenant-specific flag overrides
|
||||||
|
* @param props.onAdd - Callback invoked when adding a new override
|
||||||
|
* @param props.onRemove - Callback invoked when removing an existing override
|
||||||
|
* @param props.isLoading - Indicates if an operation is in progress
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* <TenantOverridesPanel
|
||||||
|
* flags={[
|
||||||
|
* { id: '1', key: 'enable_dark_mode', name: 'Enable Dark Mode', ... },
|
||||||
|
* { id: '2', key: 'beta_features', name: 'Beta Features', ... },
|
||||||
|
* ]}
|
||||||
|
* overrides={[
|
||||||
|
* { id: 'o1', flag_id: '1', is_enabled: true, ... },
|
||||||
|
* ]}
|
||||||
|
* onAdd={(flagId, isEnabled) => console.log('Add override', flagId, isEnabled)}
|
||||||
|
* onRemove={(flagId) => console.log('Remove override', flagId)}
|
||||||
|
* isLoading={false}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function TenantOverridesPanel({
|
export function TenantOverridesPanel({
|
||||||
flags,
|
flags,
|
||||||
overrides,
|
overrides,
|
||||||
|
|||||||
@ -1,10 +1,35 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useDevices, usePushNotifications, UserDevice } from '@/hooks/usePushNotifications';
|
import { useDevices, usePushNotifications, UserDevice } from '@/hooks/usePushNotifications';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the DevicesManager component.
|
||||||
|
*/
|
||||||
interface DevicesManagerProps {
|
interface DevicesManagerProps {
|
||||||
|
/**
|
||||||
|
* Whether to display the header section with title and description.
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
showHeader?: boolean;
|
showHeader?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages user devices registered for push notifications.
|
||||||
|
*
|
||||||
|
* @description Displays a list of devices registered to receive push notifications,
|
||||||
|
* allowing users to view device details, register new devices, and unregister existing ones.
|
||||||
|
* Shows device type icons, activity status, and last used timestamp for each device.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.showHeader - Whether to display the header section with title and "Add device" button
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Basic usage with header
|
||||||
|
* <DevicesManager />
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Without header (for embedding in settings pages)
|
||||||
|
* <DevicesManager showHeader={false} />
|
||||||
|
*/
|
||||||
export function DevicesManager({ showHeader = true }: DevicesManagerProps) {
|
export function DevicesManager({ showHeader = true }: DevicesManagerProps) {
|
||||||
const { data: devices, isLoading, error, refetch } = useDevices();
|
const { data: devices, isLoading, error, refetch } = useDevices();
|
||||||
const { unregisterDevice, isSubscribed, requestPermission, isLoading: pushLoading } = usePushNotifications();
|
const { unregisterDevice, isSubscribed, requestPermission, isLoading: pushLoading } = usePushNotifications();
|
||||||
|
|||||||
@ -4,6 +4,28 @@ import clsx from 'clsx';
|
|||||||
import { useUnreadNotificationsCount } from '@/hooks/useData';
|
import { useUnreadNotificationsCount } from '@/hooks/useData';
|
||||||
import { NotificationDrawer } from './NotificationDrawer';
|
import { NotificationDrawer } from './NotificationDrawer';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A bell icon button that displays the unread notification count and opens the notification drawer.
|
||||||
|
*
|
||||||
|
* @description Renders a clickable bell icon that shows a badge with the unread notification count.
|
||||||
|
* When clicked, it opens the NotificationDrawer component as a slide-in panel.
|
||||||
|
* The bell icon color changes based on whether there are unread notifications.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Basic usage in a header/navbar
|
||||||
|
* <header>
|
||||||
|
* <nav>
|
||||||
|
* <NotificationBell />
|
||||||
|
* </nav>
|
||||||
|
* </header>
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Usage in a toolbar
|
||||||
|
* <div className="toolbar">
|
||||||
|
* <NotificationBell />
|
||||||
|
* <UserMenu />
|
||||||
|
* </div>
|
||||||
|
*/
|
||||||
export function NotificationBell() {
|
export function NotificationBell() {
|
||||||
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
||||||
const { data } = useUnreadNotificationsCount();
|
const { data } = useUnreadNotificationsCount();
|
||||||
|
|||||||
@ -4,11 +4,41 @@ import clsx from 'clsx';
|
|||||||
import { useNotifications, useMarkNotificationAsRead, useMarkAllNotificationsAsRead } from '@/hooks/useData';
|
import { useNotifications, useMarkNotificationAsRead, useMarkAllNotificationsAsRead } from '@/hooks/useData';
|
||||||
import { NotificationItem } from './NotificationItem';
|
import { NotificationItem } from './NotificationItem';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the NotificationDrawer component.
|
||||||
|
*/
|
||||||
interface NotificationDrawerProps {
|
interface NotificationDrawerProps {
|
||||||
|
/**
|
||||||
|
* Controls the visibility of the drawer.
|
||||||
|
*/
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
/**
|
||||||
|
* Callback function invoked when the drawer should be closed.
|
||||||
|
*/
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A slide-in drawer panel that displays user notifications.
|
||||||
|
*
|
||||||
|
* @description Renders a full-height drawer that slides in from the right side of the screen.
|
||||||
|
* Displays a list of notifications with the ability to mark individual or all notifications as read.
|
||||||
|
* Includes a backdrop overlay, loading state, and empty state handling.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.isOpen - Whether the drawer is currently open/visible
|
||||||
|
* @param props.onClose - Callback to close the drawer (triggered by backdrop click or close button)
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Controlled drawer usage
|
||||||
|
* const [isOpen, setIsOpen] = useState(false);
|
||||||
|
*
|
||||||
|
* <button onClick={() => setIsOpen(true)}>Open Notifications</button>
|
||||||
|
* <NotificationDrawer
|
||||||
|
* isOpen={isOpen}
|
||||||
|
* onClose={() => setIsOpen(false)}
|
||||||
|
* />
|
||||||
|
*/
|
||||||
export function NotificationDrawer({ isOpen, onClose }: NotificationDrawerProps) {
|
export function NotificationDrawer({ isOpen, onClose }: NotificationDrawerProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { data, isLoading } = useNotifications(1, 20);
|
const { data, isLoading } = useNotifications(1, 20);
|
||||||
|
|||||||
@ -3,9 +3,24 @@ import { Bell, AlertCircle, CheckCircle, Info, AlertTriangle } from 'lucide-reac
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import type { Notification } from '@/hooks/useData';
|
import type { Notification } from '@/hooks/useData';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the NotificationItem component.
|
||||||
|
*/
|
||||||
interface NotificationItemProps {
|
interface NotificationItemProps {
|
||||||
|
/**
|
||||||
|
* The notification object to display.
|
||||||
|
*/
|
||||||
notification: Notification;
|
notification: Notification;
|
||||||
|
/**
|
||||||
|
* Callback invoked when the notification is clicked to mark it as read.
|
||||||
|
* @param id - The notification ID
|
||||||
|
*/
|
||||||
onRead: (id: string) => void;
|
onRead: (id: string) => void;
|
||||||
|
/**
|
||||||
|
* Optional callback invoked when the notification has an action URL.
|
||||||
|
* If provided and the notification has an action_url, navigation will occur.
|
||||||
|
* @param url - The URL to navigate to
|
||||||
|
*/
|
||||||
onNavigate?: (url: string) => void;
|
onNavigate?: (url: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,6 +40,33 @@ const typeColors: Record<string, string> = {
|
|||||||
default: 'text-secondary-500 bg-secondary-50 dark:bg-secondary-800',
|
default: 'text-secondary-500 bg-secondary-50 dark:bg-secondary-800',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a single notification item with icon, title, message, and timestamp.
|
||||||
|
*
|
||||||
|
* @description Displays a notification with type-based icon and coloring (info, success, warning, error).
|
||||||
|
* Shows read/unread status visually and handles click events to mark as read and optionally navigate.
|
||||||
|
* Uses relative time formatting for the timestamp (e.g., "5 minutes ago").
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.notification - The notification data including id, title, message, type, and timestamps
|
||||||
|
* @param props.onRead - Callback to mark the notification as read when clicked
|
||||||
|
* @param props.onNavigate - Optional callback for navigation when notification has an action URL
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Basic usage
|
||||||
|
* <NotificationItem
|
||||||
|
* notification={{
|
||||||
|
* id: '1',
|
||||||
|
* title: 'New message',
|
||||||
|
* message: 'You have a new message from John',
|
||||||
|
* type: 'info',
|
||||||
|
* created_at: '2026-01-25T10:00:00Z',
|
||||||
|
* read_at: null
|
||||||
|
* }}
|
||||||
|
* onRead={(id) => markAsRead(id)}
|
||||||
|
* onNavigate={(url) => navigate(url)}
|
||||||
|
* />
|
||||||
|
*/
|
||||||
export function NotificationItem({ notification, onRead, onNavigate }: NotificationItemProps) {
|
export function NotificationItem({ notification, onRead, onNavigate }: NotificationItemProps) {
|
||||||
const isRead = !!notification.read_at;
|
const isRead = !!notification.read_at;
|
||||||
const Icon = typeIcons[notification.type] || typeIcons.default;
|
const Icon = typeIcons[notification.type] || typeIcons.default;
|
||||||
|
|||||||
@ -1,10 +1,43 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { usePushNotifications } from '@/hooks/usePushNotifications';
|
import { usePushNotifications } from '@/hooks/usePushNotifications';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the PushPermissionBanner component.
|
||||||
|
*/
|
||||||
interface PushPermissionBannerProps {
|
interface PushPermissionBannerProps {
|
||||||
|
/**
|
||||||
|
* Optional callback invoked when the user dismisses the banner.
|
||||||
|
* The dismissal is also persisted to localStorage.
|
||||||
|
*/
|
||||||
onDismiss?: () => void;
|
onDismiss?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A promotional banner prompting users to enable push notifications.
|
||||||
|
*
|
||||||
|
* @description Displays a banner encouraging users to enable browser push notifications.
|
||||||
|
* Automatically hides when: push notifications are not supported, already subscribed,
|
||||||
|
* permission was denied, or the banner was previously dismissed. Stores dismissal
|
||||||
|
* state in localStorage to avoid showing repeatedly.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.onDismiss - Optional callback when user dismisses the banner (via "Not now" or close button)
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Basic usage at the top of a dashboard
|
||||||
|
* <div className="dashboard">
|
||||||
|
* <PushPermissionBanner />
|
||||||
|
* <DashboardContent />
|
||||||
|
* </div>
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // With custom dismiss handling
|
||||||
|
* <PushPermissionBanner
|
||||||
|
* onDismiss={() => {
|
||||||
|
* analytics.track('push_banner_dismissed');
|
||||||
|
* }}
|
||||||
|
* />
|
||||||
|
*/
|
||||||
export function PushPermissionBanner({ onDismiss }: PushPermissionBannerProps) {
|
export function PushPermissionBanner({ onDismiss }: PushPermissionBannerProps) {
|
||||||
const {
|
const {
|
||||||
isSupported,
|
isSupported,
|
||||||
|
|||||||
@ -3,12 +3,45 @@ import { X } from 'lucide-react';
|
|||||||
import { useCreateActivity } from '../../hooks/sales';
|
import { useCreateActivity } from '../../hooks/sales';
|
||||||
import { CreateActivityDto, ActivityType } from '../../services/sales/activities.api';
|
import { CreateActivityDto, ActivityType } from '../../services/sales/activities.api';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the ActivityForm component
|
||||||
|
*/
|
||||||
interface ActivityFormProps {
|
interface ActivityFormProps {
|
||||||
|
/** Optional lead ID to associate the activity with */
|
||||||
leadId?: string;
|
leadId?: string;
|
||||||
|
/** Optional opportunity ID to associate the activity with */
|
||||||
opportunityId?: string;
|
opportunityId?: string;
|
||||||
|
/** Callback function triggered when the form is closed or after successful submission */
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal form component for creating sales activities such as calls, meetings, tasks, emails, or notes.
|
||||||
|
* Provides fields for activity type, subject, description, due date, and duration.
|
||||||
|
* Shows conditional fields based on activity type (e.g., call direction for calls, location for meetings).
|
||||||
|
* Activities can be associated with a lead, opportunity, or both.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.leadId - Optional lead ID to link the activity
|
||||||
|
* @param props.opportunityId - Optional opportunity ID to link the activity
|
||||||
|
* @param props.onClose - Callback to close the modal
|
||||||
|
* @returns A modal form for activity creation
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* // Activity for a lead
|
||||||
|
* <ActivityForm
|
||||||
|
* leadId="lead-123"
|
||||||
|
* onClose={() => setShowForm(false)}
|
||||||
|
* />
|
||||||
|
*
|
||||||
|
* // Activity for an opportunity
|
||||||
|
* <ActivityForm
|
||||||
|
* opportunityId="opp-456"
|
||||||
|
* onClose={() => setShowForm(false)}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function ActivityForm({ leadId, opportunityId, onClose }: ActivityFormProps) {
|
export function ActivityForm({ leadId, opportunityId, onClose }: ActivityFormProps) {
|
||||||
const [formData, setFormData] = useState<CreateActivityDto>({
|
const [formData, setFormData] = useState<CreateActivityDto>({
|
||||||
type: 'task',
|
type: 'task',
|
||||||
|
|||||||
@ -1,10 +1,48 @@
|
|||||||
import { Phone, Users, CheckSquare, Mail, FileText, Clock, CheckCircle } from 'lucide-react';
|
import { Phone, Users, CheckSquare, Mail, FileText, Clock, CheckCircle } from 'lucide-react';
|
||||||
import { Activity, ActivityType, ActivityStatus } from '../../services/sales/activities.api';
|
import { Activity, ActivityType, ActivityStatus } from '../../services/sales/activities.api';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the ActivityTimeline component
|
||||||
|
*/
|
||||||
interface ActivityTimelineProps {
|
interface ActivityTimelineProps {
|
||||||
|
/** Array of activity objects to display in chronological order */
|
||||||
activities: Activity[];
|
activities: Activity[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a vertical timeline of sales activities with visual differentiation by type.
|
||||||
|
* Each activity shows an icon based on type (call, meeting, task, email, note),
|
||||||
|
* subject, description, status indicator, date, and optional outcome.
|
||||||
|
* Uses color-coded icons and a connecting timeline line for visual hierarchy.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.activities - Array of activities to display in the timeline
|
||||||
|
* @returns A timeline component showing activity history
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* <ActivityTimeline
|
||||||
|
* activities={[
|
||||||
|
* {
|
||||||
|
* id: '1',
|
||||||
|
* type: 'call',
|
||||||
|
* subject: 'Follow-up call',
|
||||||
|
* status: 'completed',
|
||||||
|
* created_at: '2026-01-15',
|
||||||
|
* outcome: 'Scheduled demo for next week'
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* id: '2',
|
||||||
|
* type: 'meeting',
|
||||||
|
* subject: 'Product demo',
|
||||||
|
* status: 'pending',
|
||||||
|
* due_date: '2026-01-22',
|
||||||
|
* created_at: '2026-01-15'
|
||||||
|
* }
|
||||||
|
* ]}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function ActivityTimeline({ activities }: ActivityTimelineProps) {
|
export function ActivityTimeline({ activities }: ActivityTimelineProps) {
|
||||||
const getIcon = (type: ActivityType) => {
|
const getIcon = (type: ActivityType) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
|||||||
@ -1,9 +1,37 @@
|
|||||||
import { ConversionRates } from '../../services/sales/dashboard.api';
|
import { ConversionRates } from '../../services/sales/dashboard.api';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the ConversionFunnel component
|
||||||
|
*/
|
||||||
interface ConversionFunnelProps {
|
interface ConversionFunnelProps {
|
||||||
|
/** Conversion rate data including stage-to-stage rates and breakdown by source */
|
||||||
conversion: ConversionRates;
|
conversion: ConversionRates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a visual sales conversion funnel with stage breakdown and rates.
|
||||||
|
* Shows a tapered funnel visualization from Leads to Opportunities to Won deals.
|
||||||
|
* Includes conversion rate metrics and optional breakdown by lead source.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.conversion - Conversion rate data for funnel visualization
|
||||||
|
* @returns A funnel chart component with conversion metrics
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* <ConversionFunnel
|
||||||
|
* conversion={{
|
||||||
|
* leadToOpportunity: 30,
|
||||||
|
* opportunityToWon: 40,
|
||||||
|
* overall: 12,
|
||||||
|
* bySource: [
|
||||||
|
* { source: 'website', rate: 15 },
|
||||||
|
* { source: 'referral', rate: 25 }
|
||||||
|
* ]
|
||||||
|
* }}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function ConversionFunnel({ conversion }: ConversionFunnelProps) {
|
export function ConversionFunnel({ conversion }: ConversionFunnelProps) {
|
||||||
const funnelStages = [
|
const funnelStages = [
|
||||||
{ name: 'Leads', value: 100, color: 'bg-blue-500' },
|
{ name: 'Leads', value: 100, color: 'bg-blue-500' },
|
||||||
|
|||||||
@ -1,10 +1,39 @@
|
|||||||
import { Lead } from '../../services/sales/leads.api';
|
import { Lead } from '../../services/sales/leads.api';
|
||||||
import { Mail, Phone, Building, Briefcase, Globe, MapPin, Star } from 'lucide-react';
|
import { Mail, Phone, Building, Briefcase, Globe, MapPin, Star } from 'lucide-react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the LeadCard component
|
||||||
|
*/
|
||||||
interface LeadCardProps {
|
interface LeadCardProps {
|
||||||
|
/** The lead data object to display */
|
||||||
lead: Lead;
|
lead: Lead;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays detailed information about a sales lead in a card format.
|
||||||
|
* Shows contact information, company details, address, lead status, source, and score.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.lead - The lead data object containing all lead information
|
||||||
|
* @returns A card component displaying the lead's complete details
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* <LeadCard
|
||||||
|
* lead={{
|
||||||
|
* id: '123',
|
||||||
|
* first_name: 'John',
|
||||||
|
* last_name: 'Doe',
|
||||||
|
* email: 'john@example.com',
|
||||||
|
* status: 'qualified',
|
||||||
|
* source: 'website',
|
||||||
|
* score: 85,
|
||||||
|
* created_at: '2026-01-01',
|
||||||
|
* updated_at: '2026-01-15'
|
||||||
|
* }}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function LeadCard({ lead }: LeadCardProps) {
|
export function LeadCard({ lead }: LeadCardProps) {
|
||||||
const getStatusColor = (status: string) => {
|
const getStatusColor = (status: string) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
|
|||||||
@ -3,11 +3,38 @@ import { X } from 'lucide-react';
|
|||||||
import { useCreateLead, useUpdateLead } from '../../hooks/sales';
|
import { useCreateLead, useUpdateLead } from '../../hooks/sales';
|
||||||
import { Lead, CreateLeadDto, LeadSource, LeadStatus } from '../../services/sales/leads.api';
|
import { Lead, CreateLeadDto, LeadSource, LeadStatus } from '../../services/sales/leads.api';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the LeadForm component
|
||||||
|
*/
|
||||||
interface LeadFormProps {
|
interface LeadFormProps {
|
||||||
|
/** Optional existing lead data for edit mode. If not provided, form operates in create mode */
|
||||||
lead?: Lead;
|
lead?: Lead;
|
||||||
|
/** Callback function triggered when the form is closed or after successful submission */
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal form component for creating or editing sales leads.
|
||||||
|
* Provides fields for lead contact information, company details, source, status, and notes.
|
||||||
|
* Handles form validation and submission via React Query mutations.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.lead - Optional existing lead data for editing
|
||||||
|
* @param props.onClose - Callback to close the modal
|
||||||
|
* @returns A modal form for lead creation/editing
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* // Create mode
|
||||||
|
* <LeadForm onClose={() => setShowForm(false)} />
|
||||||
|
*
|
||||||
|
* // Edit mode
|
||||||
|
* <LeadForm
|
||||||
|
* lead={existingLead}
|
||||||
|
* onClose={() => setShowForm(false)}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function LeadForm({ lead, onClose }: LeadFormProps) {
|
export function LeadForm({ lead, onClose }: LeadFormProps) {
|
||||||
const [formData, setFormData] = useState<CreateLeadDto>({
|
const [formData, setFormData] = useState<CreateLeadDto>({
|
||||||
first_name: lead?.first_name || '',
|
first_name: lead?.first_name || '',
|
||||||
|
|||||||
@ -1,14 +1,46 @@
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Lead } from '../../services/sales/leads.api';
|
import { Lead } from '../../services/sales/leads.api';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the LeadsList component
|
||||||
|
*/
|
||||||
interface LeadsListProps {
|
interface LeadsListProps {
|
||||||
|
/** Array of lead objects to display in the table */
|
||||||
leads: Lead[];
|
leads: Lead[];
|
||||||
|
/** Total number of leads across all pages */
|
||||||
total: number;
|
total: number;
|
||||||
|
/** Current page number (1-indexed) */
|
||||||
page: number;
|
page: number;
|
||||||
|
/** Total number of available pages */
|
||||||
totalPages: number;
|
totalPages: number;
|
||||||
|
/** Callback function triggered when user navigates to a different page */
|
||||||
onPageChange: (page: number) => void;
|
onPageChange: (page: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a paginated table of sales leads with navigation support.
|
||||||
|
* Shows lead name, company, status, source, score, and creation date.
|
||||||
|
* Clicking a row navigates to the lead detail page.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.leads - Array of leads to display
|
||||||
|
* @param props.total - Total lead count for pagination info
|
||||||
|
* @param props.page - Current page number
|
||||||
|
* @param props.totalPages - Total pages available
|
||||||
|
* @param props.onPageChange - Page navigation callback
|
||||||
|
* @returns A table component with pagination controls
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* <LeadsList
|
||||||
|
* leads={leadsData}
|
||||||
|
* total={100}
|
||||||
|
* page={1}
|
||||||
|
* totalPages={5}
|
||||||
|
* onPageChange={(newPage) => setCurrentPage(newPage)}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function LeadsList({ leads, total, page, totalPages, onPageChange }: LeadsListProps) {
|
export function LeadsList({ leads, total, page, totalPages, onPageChange }: LeadsListProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,45 @@
|
|||||||
import { DollarSign, Calendar, User } from 'lucide-react';
|
import { DollarSign, Calendar, User } from 'lucide-react';
|
||||||
import { Opportunity } from '../../services/sales/opportunities.api';
|
import { Opportunity } from '../../services/sales/opportunities.api';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the OpportunityCard component
|
||||||
|
*/
|
||||||
interface OpportunityCardProps {
|
interface OpportunityCardProps {
|
||||||
|
/** The opportunity data object to display */
|
||||||
opportunity: Opportunity;
|
opportunity: Opportunity;
|
||||||
|
/** Whether to render in compact mode with minimal information. Defaults to false */
|
||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
|
/** Optional click handler for card interaction */
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a sales opportunity in a card format with two display modes.
|
||||||
|
* Full mode shows name, company, amount, probability, close date, and contact.
|
||||||
|
* Compact mode shows only name and amount for use in dense layouts like pipeline boards.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.opportunity - The opportunity data to display
|
||||||
|
* @param props.compact - Enable compact display mode
|
||||||
|
* @param props.onClick - Click handler for card interaction
|
||||||
|
* @returns A card component displaying opportunity information
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* // Full display mode
|
||||||
|
* <OpportunityCard
|
||||||
|
* opportunity={opportunityData}
|
||||||
|
* onClick={() => navigate(`/opportunities/${opportunityData.id}`)}
|
||||||
|
* />
|
||||||
|
*
|
||||||
|
* // Compact mode for pipeline boards
|
||||||
|
* <OpportunityCard
|
||||||
|
* opportunity={opportunityData}
|
||||||
|
* compact
|
||||||
|
* onClick={() => handleSelect(opportunityData)}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function OpportunityCard({ opportunity, compact = false, onClick }: OpportunityCardProps) {
|
export function OpportunityCard({ opportunity, compact = false, onClick }: OpportunityCardProps) {
|
||||||
const formatCurrency = (amount: number) => {
|
const formatCurrency = (amount: number) => {
|
||||||
return new Intl.NumberFormat('en-US', {
|
return new Intl.NumberFormat('en-US', {
|
||||||
|
|||||||
@ -3,11 +3,39 @@ import { X } from 'lucide-react';
|
|||||||
import { useCreateOpportunity, useUpdateOpportunity } from '../../hooks/sales';
|
import { useCreateOpportunity, useUpdateOpportunity } from '../../hooks/sales';
|
||||||
import { Opportunity, CreateOpportunityDto, OpportunityStage } from '../../services/sales/opportunities.api';
|
import { Opportunity, CreateOpportunityDto, OpportunityStage } from '../../services/sales/opportunities.api';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the OpportunityForm component
|
||||||
|
*/
|
||||||
interface OpportunityFormProps {
|
interface OpportunityFormProps {
|
||||||
|
/** Optional existing opportunity data for edit mode. If not provided, form operates in create mode */
|
||||||
opportunity?: Opportunity;
|
opportunity?: Opportunity;
|
||||||
|
/** Callback function triggered when the form is closed or after successful submission */
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal form component for creating or editing sales opportunities.
|
||||||
|
* Provides fields for opportunity name, amount, currency, probability, stage,
|
||||||
|
* expected close date, contact information, and notes.
|
||||||
|
* Handles form validation and submission via React Query mutations.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.opportunity - Optional existing opportunity data for editing
|
||||||
|
* @param props.onClose - Callback to close the modal
|
||||||
|
* @returns A modal form for opportunity creation/editing
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* // Create mode
|
||||||
|
* <OpportunityForm onClose={() => setShowForm(false)} />
|
||||||
|
*
|
||||||
|
* // Edit mode
|
||||||
|
* <OpportunityForm
|
||||||
|
* opportunity={existingOpportunity}
|
||||||
|
* onClose={() => setShowForm(false)}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function OpportunityForm({ opportunity, onClose }: OpportunityFormProps) {
|
export function OpportunityForm({ opportunity, onClose }: OpportunityFormProps) {
|
||||||
const [formData, setFormData] = useState<CreateOpportunityDto>({
|
const [formData, setFormData] = useState<CreateOpportunityDto>({
|
||||||
name: opportunity?.name || '',
|
name: opportunity?.name || '',
|
||||||
|
|||||||
@ -2,10 +2,40 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
import { PipelineView } from '../../services/sales/opportunities.api';
|
import { PipelineView } from '../../services/sales/opportunities.api';
|
||||||
import { OpportunityCard } from './OpportunityCard';
|
import { OpportunityCard } from './OpportunityCard';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the PipelineBoard component
|
||||||
|
*/
|
||||||
interface PipelineBoardProps {
|
interface PipelineBoardProps {
|
||||||
|
/** Array of pipeline stages with their opportunities, counts, and totals */
|
||||||
stages: PipelineView[];
|
stages: PipelineView[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a Kanban-style sales pipeline board with opportunities organized by stage.
|
||||||
|
* Open stages (prospecting, qualification, proposal, negotiation) are shown as columns.
|
||||||
|
* Closed stages (won/lost) are displayed in a separate grid below.
|
||||||
|
* Each column shows stage name, opportunity count, and total value.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.stages - Array of pipeline stage data with opportunities
|
||||||
|
* @returns A Kanban board component for visualizing the sales pipeline
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* <PipelineBoard
|
||||||
|
* stages={[
|
||||||
|
* {
|
||||||
|
* stage: 'prospecting',
|
||||||
|
* stageName: 'Prospecting',
|
||||||
|
* count: 5,
|
||||||
|
* totalAmount: 50000,
|
||||||
|
* opportunities: [...]
|
||||||
|
* },
|
||||||
|
* // ... other stages
|
||||||
|
* ]}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function PipelineBoard({ stages }: PipelineBoardProps) {
|
export function PipelineBoard({ stages }: PipelineBoardProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
|||||||
@ -2,11 +2,44 @@ import { TrendingUp, Users, DollarSign, Target, ArrowUpRight, ArrowDownRight } f
|
|||||||
import { SalesSummary, ConversionRates } from '../../services/sales/dashboard.api';
|
import { SalesSummary, ConversionRates } from '../../services/sales/dashboard.api';
|
||||||
import { ConversionFunnel } from './ConversionFunnel';
|
import { ConversionFunnel } from './ConversionFunnel';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the SalesDashboard component
|
||||||
|
*/
|
||||||
interface SalesDashboardProps {
|
interface SalesDashboardProps {
|
||||||
|
/** Sales summary data including leads, opportunities, pipeline, and activities metrics */
|
||||||
summary: SalesSummary;
|
summary: SalesSummary;
|
||||||
|
/** Conversion rate data for the sales funnel visualization */
|
||||||
conversion: ConversionRates;
|
conversion: ConversionRates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a comprehensive sales dashboard with key metrics and visualizations.
|
||||||
|
* Includes stat cards for total leads, open opportunities, won value, and win rate.
|
||||||
|
* Shows pipeline breakdown by stage, conversion funnel, and activities overview.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.summary - Sales summary metrics data
|
||||||
|
* @param props.conversion - Conversion rates for funnel visualization
|
||||||
|
* @returns A dashboard component with sales KPIs and charts
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* <SalesDashboard
|
||||||
|
* summary={{
|
||||||
|
* leads: { total: 150, conversionRate: 25 },
|
||||||
|
* opportunities: { open: 45, won: 12, wonValue: 125000, winRate: 35, avgDealSize: 10000 },
|
||||||
|
* pipeline: { totalValue: 500000, byStage: [...] },
|
||||||
|
* activities: { total: 200, pending: 30, completed: 160, overdue: 10 }
|
||||||
|
* }}
|
||||||
|
* conversion={{
|
||||||
|
* leadToOpportunity: 30,
|
||||||
|
* opportunityToWon: 40,
|
||||||
|
* overall: 12,
|
||||||
|
* bySource: [...]
|
||||||
|
* }}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function SalesDashboard({ summary, conversion }: SalesDashboardProps) {
|
export function SalesDashboard({ summary, conversion }: SalesDashboardProps) {
|
||||||
const formatCurrency = (amount: number) => {
|
const formatCurrency = (amount: number) => {
|
||||||
return new Intl.NumberFormat('en-US', {
|
return new Intl.NumberFormat('en-US', {
|
||||||
|
|||||||
@ -15,10 +15,17 @@ import {
|
|||||||
import { StorageFile, storageApi } from '@/services/api';
|
import { StorageFile, storageApi } from '@/services/api';
|
||||||
import { useDeleteFile } from '@/hooks/useStorage';
|
import { useDeleteFile } from '@/hooks/useStorage';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the FileItem component.
|
||||||
|
*/
|
||||||
interface FileItemProps {
|
interface FileItemProps {
|
||||||
|
/** The storage file object containing metadata and content info */
|
||||||
file: StorageFile;
|
file: StorageFile;
|
||||||
|
/** Display mode: 'grid' for thumbnail cards, 'list' for horizontal rows */
|
||||||
view?: 'grid' | 'list';
|
view?: 'grid' | 'list';
|
||||||
|
/** Callback fired after successful file deletion */
|
||||||
onDelete?: () => void;
|
onDelete?: () => void;
|
||||||
|
/** Callback fired when user requests file preview (images only) */
|
||||||
onPreview?: (file: StorageFile) => void;
|
onPreview?: (file: StorageFile) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,6 +63,32 @@ function VisibilityIcon({ visibility }: { visibility: string }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a single file entry with download, delete, and preview actions.
|
||||||
|
* Supports both grid (thumbnail cards) and list (horizontal rows) view modes.
|
||||||
|
* Automatically shows appropriate icons based on file MIME type and handles
|
||||||
|
* visibility indicators (private, tenant, public).
|
||||||
|
*
|
||||||
|
* @description Renders a file item with actions for download, delete, and preview.
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.file - The storage file object to display
|
||||||
|
* @param props.view - Display mode: 'grid' or 'list' (default: 'list')
|
||||||
|
* @param props.onDelete - Optional callback after successful deletion
|
||||||
|
* @param props.onPreview - Optional callback for image preview requests
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Basic usage in list view
|
||||||
|
* <FileItem file={myFile} />
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Grid view with preview handler
|
||||||
|
* <FileItem
|
||||||
|
* file={myFile}
|
||||||
|
* view="grid"
|
||||||
|
* onPreview={(file) => openPreviewModal(file)}
|
||||||
|
* onDelete={() => refetchFiles()}
|
||||||
|
* />
|
||||||
|
*/
|
||||||
export function FileItem({ file, view = 'list', onDelete, onPreview }: FileItemProps) {
|
export function FileItem({ file, view = 'list', onDelete, onPreview }: FileItemProps) {
|
||||||
const [showMenu, setShowMenu] = useState(false);
|
const [showMenu, setShowMenu] = useState(false);
|
||||||
const [downloading, setDownloading] = useState(false);
|
const [downloading, setDownloading] = useState(false);
|
||||||
|
|||||||
@ -4,12 +4,41 @@ import { useFiles } from '@/hooks/useStorage';
|
|||||||
import { StorageFile } from '@/services/api';
|
import { StorageFile } from '@/services/api';
|
||||||
import { FileItem } from './FileItem';
|
import { FileItem } from './FileItem';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the FileList component.
|
||||||
|
*/
|
||||||
interface FileListProps {
|
interface FileListProps {
|
||||||
|
/** Optional folder path to filter files */
|
||||||
folder?: string;
|
folder?: string;
|
||||||
|
/** Callback fired when user requests file preview */
|
||||||
onPreview?: (file: StorageFile) => void;
|
onPreview?: (file: StorageFile) => void;
|
||||||
|
/** Additional CSS classes to apply to the container */
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a paginated, searchable list of files with grid/list view toggle.
|
||||||
|
* Fetches files from the storage API and provides search, pagination,
|
||||||
|
* and view mode switching. Handles loading, empty, and error states.
|
||||||
|
*
|
||||||
|
* @description Renders a file browser with search, pagination, and view modes.
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.folder - Optional folder to filter files by
|
||||||
|
* @param props.onPreview - Optional callback for file preview requests
|
||||||
|
* @param props.className - Additional CSS classes for the container
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Basic usage
|
||||||
|
* <FileList />
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // With folder filter and preview handler
|
||||||
|
* <FileList
|
||||||
|
* folder="documents"
|
||||||
|
* onPreview={(file) => setPreviewFile(file)}
|
||||||
|
* className="mt-4"
|
||||||
|
* />
|
||||||
|
*/
|
||||||
export function FileList({ folder, onPreview, className = '' }: FileListProps) {
|
export function FileList({ folder, onPreview, className = '' }: FileListProps) {
|
||||||
const [view, setView] = useState<'grid' | 'list'>('list');
|
const [view, setView] = useState<'grid' | 'list'>('list');
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
|
|||||||
@ -2,13 +2,23 @@ import { useState, useRef, useCallback } from 'react';
|
|||||||
import { Upload, X, File, Image, FileText, Table, AlertCircle } from 'lucide-react';
|
import { Upload, X, File, Image, FileText, Table, AlertCircle } from 'lucide-react';
|
||||||
import { useUploadFile } from '@/hooks/useStorage';
|
import { useUploadFile } from '@/hooks/useStorage';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the FileUpload component.
|
||||||
|
*/
|
||||||
interface FileUploadProps {
|
interface FileUploadProps {
|
||||||
|
/** Target folder for uploaded files (default: 'files') */
|
||||||
folder?: string;
|
folder?: string;
|
||||||
|
/** File visibility level: 'private', 'tenant', or 'public' (default: 'private') */
|
||||||
visibility?: 'private' | 'tenant' | 'public';
|
visibility?: 'private' | 'tenant' | 'public';
|
||||||
|
/** MIME types to accept (defaults to ALLOWED_TYPES) */
|
||||||
accept?: string;
|
accept?: string;
|
||||||
maxSize?: number; // in bytes
|
/** Maximum file size in bytes (default: 50MB) */
|
||||||
|
maxSize?: number;
|
||||||
|
/** Callback fired after successful upload with file id and filename */
|
||||||
onSuccess?: (file: { id: string; filename: string }) => void;
|
onSuccess?: (file: { id: string; filename: string }) => void;
|
||||||
|
/** Callback fired when upload fails with the error */
|
||||||
onError?: (error: Error) => void;
|
onError?: (error: Error) => void;
|
||||||
|
/** Additional CSS classes to apply to the container */
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,6 +52,37 @@ function formatBytes(bytes: number): string {
|
|||||||
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drag-and-drop file upload component with progress indicator.
|
||||||
|
* Supports file validation (type and size), visual drag feedback,
|
||||||
|
* upload progress display, and error handling. Files can be selected
|
||||||
|
* via drag-and-drop or click-to-browse.
|
||||||
|
*
|
||||||
|
* @description Renders a drag-and-drop zone for uploading files with progress.
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.folder - Target folder for uploads (default: 'files')
|
||||||
|
* @param props.visibility - File visibility: 'private', 'tenant', or 'public'
|
||||||
|
* @param props.accept - MIME types to accept
|
||||||
|
* @param props.maxSize - Maximum file size in bytes (default: 50MB)
|
||||||
|
* @param props.onSuccess - Callback after successful upload
|
||||||
|
* @param props.onError - Callback on upload error
|
||||||
|
* @param props.className - Additional CSS classes
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Basic usage
|
||||||
|
* <FileUpload onSuccess={(file) => console.log('Uploaded:', file.id)} />
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // With custom settings
|
||||||
|
* <FileUpload
|
||||||
|
* folder="avatars"
|
||||||
|
* visibility="public"
|
||||||
|
* maxSize={5 * 1024 * 1024}
|
||||||
|
* accept="image/*"
|
||||||
|
* onSuccess={handleUpload}
|
||||||
|
* onError={(err) => toast.error(err.message)}
|
||||||
|
* />
|
||||||
|
*/
|
||||||
export function FileUpload({
|
export function FileUpload({
|
||||||
folder = 'files',
|
folder = 'files',
|
||||||
visibility = 'private',
|
visibility = 'private',
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
import { HardDrive, Folder } from 'lucide-react';
|
import { HardDrive, Folder } from 'lucide-react';
|
||||||
import { useStorageUsage } from '@/hooks/useStorage';
|
import { useStorageUsage } from '@/hooks/useStorage';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the StorageUsageCard component.
|
||||||
|
*/
|
||||||
interface StorageUsageCardProps {
|
interface StorageUsageCardProps {
|
||||||
|
/** Additional CSS classes to apply to the card container */
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -12,6 +16,24 @@ function formatBytes(bytes: number): string {
|
|||||||
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays storage usage statistics in a card format.
|
||||||
|
* Shows total bytes used, usage percentage with color-coded progress bar,
|
||||||
|
* file count, max file size, and breakdown by folder. Handles loading
|
||||||
|
* and error states gracefully.
|
||||||
|
*
|
||||||
|
* @description Renders a card showing storage quota usage and file statistics.
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.className - Additional CSS classes for the card container
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Basic usage
|
||||||
|
* <StorageUsageCard />
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // With custom styling
|
||||||
|
* <StorageUsageCard className="col-span-2 shadow-lg" />
|
||||||
|
*/
|
||||||
export function StorageUsageCard({ className = '' }: StorageUsageCardProps) {
|
export function StorageUsageCard({ className = '' }: StorageUsageCardProps) {
|
||||||
const { data, isLoading, error } = useStorageUsage();
|
const { data, isLoading, error } = useStorageUsage();
|
||||||
|
|
||||||
|
|||||||
@ -14,15 +14,50 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the WebhookCard component.
|
||||||
|
*/
|
||||||
interface WebhookCardProps {
|
interface WebhookCardProps {
|
||||||
|
/** The webhook data to display */
|
||||||
webhook: Webhook;
|
webhook: Webhook;
|
||||||
|
/** Callback invoked when the user clicks the edit action */
|
||||||
onEdit: (webhook: Webhook) => void;
|
onEdit: (webhook: Webhook) => void;
|
||||||
|
/** Callback invoked when the user clicks the delete action */
|
||||||
onDelete: (webhook: Webhook) => void;
|
onDelete: (webhook: Webhook) => void;
|
||||||
|
/** Callback invoked when the user clicks the test/send action */
|
||||||
onTest: (webhook: Webhook) => void;
|
onTest: (webhook: Webhook) => void;
|
||||||
|
/** Callback invoked when the user toggles the webhook active state */
|
||||||
onToggle: (webhook: Webhook) => void;
|
onToggle: (webhook: Webhook) => void;
|
||||||
|
/** Callback invoked when the user wants to view delivery history */
|
||||||
onViewDeliveries: (webhook: Webhook) => void;
|
onViewDeliveries: (webhook: Webhook) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a single webhook configuration as a card with status, events, and statistics.
|
||||||
|
*
|
||||||
|
* @description Renders a card showing webhook details including name, URL, subscribed events,
|
||||||
|
* delivery statistics, and a dropdown menu with actions (edit, test, view deliveries, toggle, delete).
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.webhook - The webhook data to display
|
||||||
|
* @param props.onEdit - Callback when user clicks edit
|
||||||
|
* @param props.onDelete - Callback when user clicks delete
|
||||||
|
* @param props.onTest - Callback when user clicks send test
|
||||||
|
* @param props.onToggle - Callback when user toggles active state
|
||||||
|
* @param props.onViewDeliveries - Callback when user clicks view deliveries
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* <WebhookCard
|
||||||
|
* webhook={webhookData}
|
||||||
|
* onEdit={(wh) => openEditModal(wh)}
|
||||||
|
* onDelete={(wh) => confirmDelete(wh)}
|
||||||
|
* onTest={(wh) => sendTestWebhook(wh)}
|
||||||
|
* onToggle={(wh) => toggleWebhookStatus(wh)}
|
||||||
|
* onViewDeliveries={(wh) => showDeliveryHistory(wh)}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function WebhookCard({
|
export function WebhookCard({
|
||||||
webhook,
|
webhook,
|
||||||
onEdit,
|
onEdit,
|
||||||
|
|||||||
@ -11,11 +11,19 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the WebhookDeliveryList component.
|
||||||
|
*/
|
||||||
interface WebhookDeliveryListProps {
|
interface WebhookDeliveryListProps {
|
||||||
|
/** Array of webhook delivery records to display */
|
||||||
deliveries: WebhookDelivery[];
|
deliveries: WebhookDelivery[];
|
||||||
|
/** Whether the list is currently loading data */
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
|
/** Callback invoked when the user requests to retry a failed delivery */
|
||||||
onRetry: (deliveryId: string) => void;
|
onRetry: (deliveryId: string) => void;
|
||||||
|
/** Callback invoked when the user requests to load more deliveries (pagination) */
|
||||||
onLoadMore?: () => void;
|
onLoadMore?: () => void;
|
||||||
|
/** Indicates whether there are more deliveries available to load */
|
||||||
hasMore?: boolean;
|
hasMore?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,6 +53,16 @@ const statusConfig: Record<
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a single delivery record as an expandable row.
|
||||||
|
*
|
||||||
|
* @description Displays delivery summary (event type, timestamp, status) with expand/collapse
|
||||||
|
* functionality to show detailed information including payload, response, and retry options.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.delivery - The webhook delivery record to display
|
||||||
|
* @param props.onRetry - Callback when user clicks retry on a failed delivery
|
||||||
|
*/
|
||||||
function DeliveryRow({
|
function DeliveryRow({
|
||||||
delivery,
|
delivery,
|
||||||
onRetry,
|
onRetry,
|
||||||
@ -183,6 +201,31 @@ function DeliveryRow({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a paginated list of webhook delivery attempts with their status and details.
|
||||||
|
*
|
||||||
|
* @description Renders a list of delivery records showing status (delivered, failed, pending, retrying),
|
||||||
|
* event type, timestamps, payload data, and response information. Supports pagination via "Load More"
|
||||||
|
* and allows retrying failed deliveries.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.deliveries - Array of webhook delivery records to display
|
||||||
|
* @param props.isLoading - Whether data is currently being loaded
|
||||||
|
* @param props.onRetry - Callback when user requests to retry a failed delivery
|
||||||
|
* @param props.onLoadMore - Optional callback to load more deliveries (pagination)
|
||||||
|
* @param props.hasMore - Whether there are more deliveries available to load
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* <WebhookDeliveryList
|
||||||
|
* deliveries={webhookDeliveries}
|
||||||
|
* isLoading={isLoadingDeliveries}
|
||||||
|
* onRetry={(deliveryId) => retryDelivery(deliveryId)}
|
||||||
|
* onLoadMore={() => fetchMoreDeliveries()}
|
||||||
|
* hasMore={hasMoreDeliveries}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function WebhookDeliveryList({
|
export function WebhookDeliveryList({
|
||||||
deliveries,
|
deliveries,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
|||||||
@ -3,14 +3,58 @@ import { Webhook, CreateWebhookRequest, UpdateWebhookRequest, WebhookEvent } fro
|
|||||||
import { X, Plus, Trash2, Eye, EyeOff, Copy, Check } from 'lucide-react';
|
import { X, Plus, Trash2, Eye, EyeOff, Copy, Check } from 'lucide-react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the WebhookForm component.
|
||||||
|
*/
|
||||||
interface WebhookFormProps {
|
interface WebhookFormProps {
|
||||||
|
/** Existing webhook data for editing, or null/undefined for creating a new webhook */
|
||||||
webhook?: Webhook | null;
|
webhook?: Webhook | null;
|
||||||
|
/** List of available webhook events that can be subscribed to */
|
||||||
events: WebhookEvent[];
|
events: WebhookEvent[];
|
||||||
|
/** Callback invoked when the form is submitted with valid data */
|
||||||
onSubmit: (data: CreateWebhookRequest | UpdateWebhookRequest) => void;
|
onSubmit: (data: CreateWebhookRequest | UpdateWebhookRequest) => void;
|
||||||
|
/** Callback invoked when the user cancels the form */
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
|
/** Whether the form submission is in progress */
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Form component for creating or editing webhook configurations.
|
||||||
|
*
|
||||||
|
* @description Provides a comprehensive form for webhook management including:
|
||||||
|
* - Name and description fields
|
||||||
|
* - HTTPS endpoint URL with validation
|
||||||
|
* - Event selection checkboxes
|
||||||
|
* - Custom HTTP headers management
|
||||||
|
* - Signing secret display (edit mode only)
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.webhook - Existing webhook data for editing, or null for creating
|
||||||
|
* @param props.events - List of available webhook events to subscribe to
|
||||||
|
* @param props.onSubmit - Callback when form is submitted with valid data
|
||||||
|
* @param props.onCancel - Callback when user cancels the form
|
||||||
|
* @param props.isLoading - Whether submission is in progress (disables form)
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* // Creating a new webhook
|
||||||
|
* <WebhookForm
|
||||||
|
* events={availableEvents}
|
||||||
|
* onSubmit={(data) => createWebhook(data)}
|
||||||
|
* onCancel={() => closeModal()}
|
||||||
|
* />
|
||||||
|
*
|
||||||
|
* // Editing an existing webhook
|
||||||
|
* <WebhookForm
|
||||||
|
* webhook={existingWebhook}
|
||||||
|
* events={availableEvents}
|
||||||
|
* onSubmit={(data) => updateWebhook(existingWebhook.id, data)}
|
||||||
|
* onCancel={() => closeModal()}
|
||||||
|
* isLoading={isSaving}
|
||||||
|
* />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
export function WebhookForm({
|
export function WebhookForm({
|
||||||
webhook,
|
webhook,
|
||||||
events,
|
events,
|
||||||
|
|||||||
@ -1,10 +1,46 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useTestWhatsAppConnection } from '../../hooks/useWhatsApp';
|
import { useTestWhatsAppConnection } from '../../hooks/useWhatsApp';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the WhatsAppTestMessage component.
|
||||||
|
*/
|
||||||
interface WhatsAppTestMessageProps {
|
interface WhatsAppTestMessageProps {
|
||||||
|
/**
|
||||||
|
* When true, disables the input field and submit button.
|
||||||
|
* Useful when the WhatsApp integration is not configured.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A form component for testing WhatsApp Business API connectivity.
|
||||||
|
*
|
||||||
|
* Allows users to send a test message to a specified phone number in E.164 format
|
||||||
|
* to verify that the WhatsApp integration is working correctly.
|
||||||
|
*
|
||||||
|
* @description Renders a card with a phone number input and submit button.
|
||||||
|
* Uses the useTestWhatsAppConnection hook to send the test message.
|
||||||
|
* Shows loading state while the message is being sent.
|
||||||
|
*
|
||||||
|
* @param props - The component props
|
||||||
|
* @param props.disabled - Optional flag to disable the form controls
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Basic usage
|
||||||
|
* <WhatsAppTestMessage />
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Disabled state when integration is not configured
|
||||||
|
* <WhatsAppTestMessage disabled={true} />
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Usage within a settings panel
|
||||||
|
* <div className="settings-panel">
|
||||||
|
* <WhatsAppConfig />
|
||||||
|
* <WhatsAppTestMessage disabled={!isConfigured} />
|
||||||
|
* </div>
|
||||||
|
*/
|
||||||
export function WhatsAppTestMessage({ disabled }: WhatsAppTestMessageProps) {
|
export function WhatsAppTestMessage({ disabled }: WhatsAppTestMessageProps) {
|
||||||
const [phoneNumber, setPhoneNumber] = useState('');
|
const [phoneNumber, setPhoneNumber] = useState('');
|
||||||
const testConnection = useTestWhatsAppConnection();
|
const testConnection = useTestWhatsAppConnection();
|
||||||
|
|||||||
@ -30,6 +30,21 @@ import { TenantsPage, TenantDetailPage, MetricsPage } from '@/pages/superadmin';
|
|||||||
// Onboarding
|
// Onboarding
|
||||||
import { OnboardingPage } from '@/pages/onboarding';
|
import { OnboardingPage } from '@/pages/onboarding';
|
||||||
|
|
||||||
|
// Sales pages
|
||||||
|
import SalesPage from '@/pages/sales';
|
||||||
|
import LeadsPage from '@/pages/sales/leads';
|
||||||
|
import LeadDetailPage from '@/pages/sales/leads/[id]';
|
||||||
|
import OpportunitiesPage from '@/pages/sales/opportunities';
|
||||||
|
import OpportunityDetailPage from '@/pages/sales/opportunities/[id]';
|
||||||
|
import ActivitiesPage from '@/pages/sales/activities';
|
||||||
|
|
||||||
|
// Commissions pages
|
||||||
|
import CommissionsPage from '@/pages/commissions';
|
||||||
|
import SchemesPage from '@/pages/commissions/schemes';
|
||||||
|
import EntriesPage from '@/pages/commissions/entries';
|
||||||
|
import PeriodsPage from '@/pages/commissions/periods';
|
||||||
|
import MyEarningsPage from '@/pages/commissions/my-earnings';
|
||||||
|
|
||||||
// Protected Route wrapper
|
// Protected Route wrapper
|
||||||
function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
||||||
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
|
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
|
||||||
@ -108,6 +123,21 @@ export function AppRouter() {
|
|||||||
<Route path="audit" element={<AuditLogsPage />} />
|
<Route path="audit" element={<AuditLogsPage />} />
|
||||||
<Route path="feature-flags" element={<FeatureFlagsPage />} />
|
<Route path="feature-flags" element={<FeatureFlagsPage />} />
|
||||||
<Route path="whatsapp" element={<WhatsAppSettings />} />
|
<Route path="whatsapp" element={<WhatsAppSettings />} />
|
||||||
|
|
||||||
|
{/* Sales routes */}
|
||||||
|
<Route path="sales" element={<SalesPage />} />
|
||||||
|
<Route path="sales/leads" element={<LeadsPage />} />
|
||||||
|
<Route path="sales/leads/:id" element={<LeadDetailPage />} />
|
||||||
|
<Route path="sales/opportunities" element={<OpportunitiesPage />} />
|
||||||
|
<Route path="sales/opportunities/:id" element={<OpportunityDetailPage />} />
|
||||||
|
<Route path="sales/activities" element={<ActivitiesPage />} />
|
||||||
|
|
||||||
|
{/* Commissions routes */}
|
||||||
|
<Route path="commissions" element={<CommissionsPage />} />
|
||||||
|
<Route path="commissions/schemes" element={<SchemesPage />} />
|
||||||
|
<Route path="commissions/entries" element={<EntriesPage />} />
|
||||||
|
<Route path="commissions/periods" element={<PeriodsPage />} />
|
||||||
|
<Route path="commissions/my-earnings" element={<MyEarningsPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
{/* Superadmin routes */}
|
{/* Superadmin routes */}
|
||||||
|
|||||||
@ -10,6 +10,11 @@ export interface User {
|
|||||||
tenant_id: string;
|
tenant_id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UpdateProfileData {
|
||||||
|
first_name?: string;
|
||||||
|
last_name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AuthState {
|
export interface AuthState {
|
||||||
user: User | null;
|
user: User | null;
|
||||||
accessToken: string | null;
|
accessToken: string | null;
|
||||||
@ -23,6 +28,8 @@ export interface AuthState {
|
|||||||
login: (user: User, accessToken: string, refreshToken: string) => void;
|
login: (user: User, accessToken: string, refreshToken: string) => void;
|
||||||
logout: () => void;
|
logout: () => void;
|
||||||
setLoading: (loading: boolean) => void;
|
setLoading: (loading: boolean) => void;
|
||||||
|
refreshTokens: () => Promise<boolean>;
|
||||||
|
updateProfile: (data: UpdateProfileData) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAuthStore = create<AuthState>()(
|
export const useAuthStore = create<AuthState>()(
|
||||||
@ -57,6 +64,57 @@ export const useAuthStore = create<AuthState>()(
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
setLoading: (isLoading) => set({ isLoading }),
|
setLoading: (isLoading) => set({ isLoading }),
|
||||||
|
|
||||||
|
refreshTokens: async () => {
|
||||||
|
const state = useAuthStore.getState();
|
||||||
|
if (!state.refreshToken) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`${import.meta.env.VITE_API_URL || '/api/v1'}/auth/refresh`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ refreshToken: state.refreshToken }),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to refresh token');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
set({
|
||||||
|
accessToken: data.accessToken,
|
||||||
|
refreshToken: data.refreshToken,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
set({
|
||||||
|
user: null,
|
||||||
|
accessToken: null,
|
||||||
|
refreshToken: null,
|
||||||
|
isAuthenticated: false,
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateProfile: (data) => {
|
||||||
|
set((state) => ({
|
||||||
|
user: state.user
|
||||||
|
? {
|
||||||
|
...state.user,
|
||||||
|
...(data.first_name !== undefined && { first_name: data.first_name }),
|
||||||
|
...(data.last_name !== undefined && { last_name: data.last_name }),
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
}));
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'auth-storage',
|
name: 'auth-storage',
|
||||||
|
|||||||
804
docs/05-frontend/FRONTEND-PAGES-SPEC.md
Normal file
804
docs/05-frontend/FRONTEND-PAGES-SPEC.md
Normal file
@ -0,0 +1,804 @@
|
|||||||
|
# Especificación de Páginas Frontend - Template SaaS
|
||||||
|
|
||||||
|
**Versión:** 1.0.0
|
||||||
|
**Actualizado:** 2026-01-25
|
||||||
|
**Total Páginas:** 38
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Índice
|
||||||
|
|
||||||
|
1. [Auth Pages](#1-auth-pages)
|
||||||
|
2. [Dashboard Pages](#2-dashboard-pages)
|
||||||
|
3. [Sales Pages](#3-sales-pages)
|
||||||
|
4. [Commissions Pages](#4-commissions-pages)
|
||||||
|
5. [Settings Pages](#5-settings-pages)
|
||||||
|
6. [Superadmin Pages](#6-superadmin-pages)
|
||||||
|
7. [Onboarding Pages](#7-onboarding-pages)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Auth Pages
|
||||||
|
|
||||||
|
### 1.1 LoginPage
|
||||||
|
|
||||||
|
**Ruta:** `/auth/login`
|
||||||
|
**Archivo:** `src/pages/auth/LoginPage.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Autenticación de usuarios existentes |
|
||||||
|
| **Layout** | AuthLayout |
|
||||||
|
| **Guard** | GuestRoute |
|
||||||
|
| **Componentes** | LoginForm, OAuthButtons |
|
||||||
|
| **Hooks** | useAuth, useForm |
|
||||||
|
| **APIs** | `authApi.login()` |
|
||||||
|
|
||||||
|
**Estados:**
|
||||||
|
- `idle` - Formulario disponible
|
||||||
|
- `loading` - Enviando credenciales
|
||||||
|
- `error` - Credenciales inválidas
|
||||||
|
- `success` - Redirige a `/dashboard`
|
||||||
|
|
||||||
|
**Campos:**
|
||||||
|
- Email (required, email format)
|
||||||
|
- Password (required, min 8 chars)
|
||||||
|
- Remember me (checkbox)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1.2 RegisterPage
|
||||||
|
|
||||||
|
**Ruta:** `/auth/register`
|
||||||
|
**Archivo:** `src/pages/auth/RegisterPage.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Registro de nuevos usuarios |
|
||||||
|
| **Layout** | AuthLayout |
|
||||||
|
| **Guard** | GuestRoute |
|
||||||
|
| **Componentes** | RegisterForm, OAuthButtons |
|
||||||
|
| **Hooks** | useAuth, useForm |
|
||||||
|
| **APIs** | `authApi.register()` |
|
||||||
|
|
||||||
|
**Estados:**
|
||||||
|
- `idle` - Formulario disponible
|
||||||
|
- `loading` - Creando cuenta
|
||||||
|
- `error` - Email duplicado u otro error
|
||||||
|
- `success` - Redirige a `/onboarding`
|
||||||
|
|
||||||
|
**Campos:**
|
||||||
|
- First name (optional)
|
||||||
|
- Last name (optional)
|
||||||
|
- Email (required, email format)
|
||||||
|
- Password (required, min 8 chars, complexity)
|
||||||
|
- Confirm password (must match)
|
||||||
|
- Terms acceptance (required)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1.3 ForgotPasswordPage
|
||||||
|
|
||||||
|
**Ruta:** `/auth/forgot-password`
|
||||||
|
**Archivo:** `src/pages/auth/ForgotPasswordPage.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Solicitar reset de contraseña |
|
||||||
|
| **Layout** | AuthLayout |
|
||||||
|
| **Guard** | GuestRoute |
|
||||||
|
| **Componentes** | Form básico |
|
||||||
|
| **Hooks** | useForm |
|
||||||
|
| **APIs** | `authApi.requestPasswordReset()` |
|
||||||
|
|
||||||
|
**Estados:**
|
||||||
|
- `idle` - Formulario disponible
|
||||||
|
- `loading` - Enviando solicitud
|
||||||
|
- `success` - Muestra mensaje de email enviado
|
||||||
|
- `error` - Email no encontrado
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Dashboard Pages
|
||||||
|
|
||||||
|
### 2.1 DashboardPage
|
||||||
|
|
||||||
|
**Ruta:** `/dashboard`
|
||||||
|
**Archivo:** `src/pages/dashboard/DashboardPage.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Vista principal con métricas y acciones rápidas |
|
||||||
|
| **Layout** | DashboardLayout |
|
||||||
|
| **Guard** | ProtectedRoute |
|
||||||
|
| **Componentes** | MetricCard, TrendChart, QuickActions |
|
||||||
|
| **Hooks** | useAuth, useSubscription, useNotifications |
|
||||||
|
| **APIs** | `billingApi.getSummary()`, `notificationsApi.getUnreadCount()` |
|
||||||
|
|
||||||
|
**Secciones:**
|
||||||
|
- Welcome banner con nombre de usuario
|
||||||
|
- Métricas principales (usuarios, storage, API calls)
|
||||||
|
- Gráfico de actividad reciente
|
||||||
|
- Acciones rápidas
|
||||||
|
- Notificaciones recientes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.2 BillingPage
|
||||||
|
|
||||||
|
**Ruta:** `/dashboard/billing`
|
||||||
|
**Archivo:** `src/pages/dashboard/BillingPage.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Gestión de suscripción y pagos |
|
||||||
|
| **Layout** | DashboardLayout |
|
||||||
|
| **Guard** | ProtectedRoute |
|
||||||
|
| **Componentes** | SubscriptionCard, InvoiceList, PaymentMethodCard |
|
||||||
|
| **Hooks** | useSubscription, useInvoices, useBillingPortal |
|
||||||
|
| **APIs** | `billingApi.*`, `stripeApi.*` |
|
||||||
|
|
||||||
|
**Secciones:**
|
||||||
|
- Plan actual y estado
|
||||||
|
- Uso del mes (límites)
|
||||||
|
- Métodos de pago
|
||||||
|
- Historial de facturas
|
||||||
|
- Botón "Manage Subscription" (Stripe Portal)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.3 UsersPage
|
||||||
|
|
||||||
|
**Ruta:** `/dashboard/users`
|
||||||
|
**Archivo:** `src/pages/dashboard/UsersPage.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Gestión de usuarios del tenant |
|
||||||
|
| **Layout** | DashboardLayout |
|
||||||
|
| **Guard** | ProtectedRoute |
|
||||||
|
| **Componentes** | UserTable, InviteModal, RoleSelect |
|
||||||
|
| **Hooks** | useUsers, useRoles |
|
||||||
|
| **APIs** | `usersApi.list()`, `usersApi.invite()`, `usersApi.update()` |
|
||||||
|
|
||||||
|
**Acciones:**
|
||||||
|
- Listar usuarios
|
||||||
|
- Invitar nuevos usuarios
|
||||||
|
- Cambiar roles
|
||||||
|
- Desactivar usuarios
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.4 AIPage
|
||||||
|
|
||||||
|
**Ruta:** `/dashboard/ai`
|
||||||
|
**Archivo:** `src/pages/dashboard/AIPage.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Chat con LLM y configuración |
|
||||||
|
| **Layout** | DashboardLayout |
|
||||||
|
| **Guard** | ProtectedRoute |
|
||||||
|
| **Componentes** | AIChat, AISettings, ChatMessage |
|
||||||
|
| **Hooks** | useAI, useAIChat, useAIUsage |
|
||||||
|
| **APIs** | `aiApi.chat()`, `aiApi.getConfig()`, `aiApi.getCurrentUsage()` |
|
||||||
|
|
||||||
|
**Secciones:**
|
||||||
|
- Chat interface con historial
|
||||||
|
- Selector de modelo
|
||||||
|
- Uso actual (tokens, costo)
|
||||||
|
- Configuración (temperatura, max tokens)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.5 StoragePage
|
||||||
|
|
||||||
|
**Ruta:** `/dashboard/storage`
|
||||||
|
**Archivo:** `src/pages/dashboard/StoragePage.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Gestión de archivos |
|
||||||
|
| **Layout** | DashboardLayout |
|
||||||
|
| **Guard** | ProtectedRoute |
|
||||||
|
| **Componentes** | FileList, FileUpload, FileItem, StorageUsageCard |
|
||||||
|
| **Hooks** | useFiles, useUpload, useStorageUsage |
|
||||||
|
| **APIs** | `storageApi.listFiles()`, `storageApi.uploadFile()`, `storageApi.deleteFile()` |
|
||||||
|
|
||||||
|
**Acciones:**
|
||||||
|
- Subir archivos (drag & drop)
|
||||||
|
- Listar archivos con paginación
|
||||||
|
- Filtrar por carpeta/tipo
|
||||||
|
- Descargar archivos
|
||||||
|
- Eliminar archivos
|
||||||
|
- Ver uso de storage
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.6 WebhooksPage
|
||||||
|
|
||||||
|
**Ruta:** `/dashboard/webhooks`
|
||||||
|
**Archivo:** `src/pages/dashboard/WebhooksPage.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Configuración de webhooks outbound |
|
||||||
|
| **Layout** | DashboardLayout |
|
||||||
|
| **Guard** | ProtectedRoute |
|
||||||
|
| **Componentes** | WebhookCard, WebhookForm, WebhookDeliveryList |
|
||||||
|
| **Hooks** | useWebhooks, useWebhookDeliveries |
|
||||||
|
| **APIs** | `webhooksApi.*` |
|
||||||
|
|
||||||
|
**Acciones:**
|
||||||
|
- Crear webhook
|
||||||
|
- Editar URL, eventos, headers
|
||||||
|
- Ver historial de entregas
|
||||||
|
- Reintentar entregas fallidas
|
||||||
|
- Test webhook
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.7 AuditLogsPage
|
||||||
|
|
||||||
|
**Ruta:** `/dashboard/audit`
|
||||||
|
**Archivo:** `src/pages/dashboard/AuditLogsPage.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Registro de auditoría de acciones |
|
||||||
|
| **Layout** | DashboardLayout |
|
||||||
|
| **Guard** | ProtectedRoute |
|
||||||
|
| **Componentes** | AuditLogRow, AuditFilters, AuditStatsCard, ActivityTimeline |
|
||||||
|
| **Hooks** | useAuditLogs, useActivityLogs |
|
||||||
|
| **APIs** | `auditApi.queryLogs()`, `auditApi.getStats()` |
|
||||||
|
|
||||||
|
**Filtros:**
|
||||||
|
- Por usuario
|
||||||
|
- Por acción (create, update, delete, login)
|
||||||
|
- Por entidad
|
||||||
|
- Por fecha
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.8 FeatureFlagsPage
|
||||||
|
|
||||||
|
**Ruta:** `/dashboard/feature-flags`
|
||||||
|
**Archivo:** `src/pages/dashboard/FeatureFlagsPage.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Gestión de feature toggles |
|
||||||
|
| **Layout** | DashboardLayout |
|
||||||
|
| **Guard** | ProtectedRoute |
|
||||||
|
| **Componentes** | FeatureFlagCard, FeatureFlagForm, TenantOverridesPanel |
|
||||||
|
| **Hooks** | useFeatureFlags, useFeatureFlag |
|
||||||
|
| **APIs** | `featureFlagsApi.*` |
|
||||||
|
|
||||||
|
**Acciones:**
|
||||||
|
- Listar flags
|
||||||
|
- Crear/editar flags
|
||||||
|
- Toggle on/off
|
||||||
|
- Configurar overrides por tenant/usuario
|
||||||
|
- Ver targeting rules
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.9 WhatsAppSettings
|
||||||
|
|
||||||
|
**Ruta:** `/dashboard/whatsapp`
|
||||||
|
**Archivo:** `src/pages/admin/WhatsAppSettings.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Configuración WhatsApp Business API |
|
||||||
|
| **Layout** | DashboardLayout |
|
||||||
|
| **Guard** | ProtectedRoute |
|
||||||
|
| **Componentes** | WhatsAppTestMessage |
|
||||||
|
| **Hooks** | useWhatsApp |
|
||||||
|
| **APIs** | `whatsappApi.*` |
|
||||||
|
|
||||||
|
**Secciones:**
|
||||||
|
- Estado de conexión
|
||||||
|
- Configuración de API
|
||||||
|
- Envío de mensaje de prueba
|
||||||
|
- Templates de mensajes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Sales Pages
|
||||||
|
|
||||||
|
### 3.1 SalesPage (Dashboard)
|
||||||
|
|
||||||
|
**Ruta:** `/dashboard/sales`
|
||||||
|
**Archivo:** `src/pages/sales/index.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Dashboard de ventas con métricas |
|
||||||
|
| **Layout** | DashboardLayout |
|
||||||
|
| **Guard** | ProtectedRoute |
|
||||||
|
| **Componentes** | SalesDashboard |
|
||||||
|
| **Hooks** | useSalesSummary, useConversionRates |
|
||||||
|
| **APIs** | Sales Dashboard API |
|
||||||
|
|
||||||
|
**Métricas:**
|
||||||
|
- Total leads
|
||||||
|
- Conversion rate
|
||||||
|
- Pipeline value
|
||||||
|
- Deals cerrados
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.2 LeadsPage
|
||||||
|
|
||||||
|
**Ruta:** `/dashboard/sales/leads`
|
||||||
|
**Archivo:** `src/pages/sales/leads/index.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Listado y gestión de leads |
|
||||||
|
| **Layout** | DashboardLayout |
|
||||||
|
| **Guard** | ProtectedRoute |
|
||||||
|
| **Componentes** | LeadsList, LeadForm |
|
||||||
|
| **Hooks** | useLeads, useLeadStats |
|
||||||
|
| **APIs** | Leads API |
|
||||||
|
|
||||||
|
**Filtros:**
|
||||||
|
- Search (nombre, email, empresa)
|
||||||
|
- Status (new, contacted, qualified, unqualified, converted)
|
||||||
|
- Source (website, referral, cold_call, event, advertisement, social_media)
|
||||||
|
|
||||||
|
**Acciones:**
|
||||||
|
- Crear lead
|
||||||
|
- Ver detalle
|
||||||
|
- Filtrar y buscar
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.3 LeadDetailPage
|
||||||
|
|
||||||
|
**Ruta:** `/dashboard/sales/leads/:id`
|
||||||
|
**Archivo:** `src/pages/sales/leads/[id].tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Detalle y edición de un lead |
|
||||||
|
| **Layout** | DashboardLayout |
|
||||||
|
| **Guard** | ProtectedRoute |
|
||||||
|
| **Componentes** | LeadCard, LeadForm, ActivityTimeline, ActivityForm |
|
||||||
|
| **Hooks** | useLead, useDeleteLead, useConvertLead, useLeadActivities |
|
||||||
|
| **APIs** | Leads API, Activities API |
|
||||||
|
|
||||||
|
**Secciones:**
|
||||||
|
- Información del lead
|
||||||
|
- Score
|
||||||
|
- Información de contacto
|
||||||
|
- Timeline de actividades
|
||||||
|
- Notas
|
||||||
|
|
||||||
|
**Acciones:**
|
||||||
|
- Editar lead
|
||||||
|
- Eliminar lead
|
||||||
|
- Convertir a oportunidad
|
||||||
|
- Agregar actividad
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.4 OpportunitiesPage
|
||||||
|
|
||||||
|
**Ruta:** `/dashboard/sales/opportunities`
|
||||||
|
**Archivo:** `src/pages/sales/opportunities/index.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Pipeline de oportunidades (Kanban) |
|
||||||
|
| **Layout** | DashboardLayout |
|
||||||
|
| **Guard** | ProtectedRoute |
|
||||||
|
| **Componentes** | PipelineBoard, OpportunityForm |
|
||||||
|
| **Hooks** | usePipeline, useOpportunityStats |
|
||||||
|
| **APIs** | Pipeline API, Opportunities API |
|
||||||
|
|
||||||
|
**Vistas:**
|
||||||
|
- Pipeline (Kanban)
|
||||||
|
- List view
|
||||||
|
|
||||||
|
**Métricas:**
|
||||||
|
- Open
|
||||||
|
- Won
|
||||||
|
- Win Rate
|
||||||
|
- Avg Deal Size
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.5 OpportunityDetailPage
|
||||||
|
|
||||||
|
**Ruta:** `/dashboard/sales/opportunities/:id`
|
||||||
|
**Archivo:** `src/pages/sales/opportunities/[id].tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Detalle y gestión de una oportunidad |
|
||||||
|
| **Layout** | DashboardLayout |
|
||||||
|
| **Guard** | ProtectedRoute |
|
||||||
|
| **Componentes** | OpportunityForm, ActivityTimeline, ActivityForm |
|
||||||
|
| **Hooks** | useOpportunity, useDeleteOpportunity, useMarkAsWon, useMarkAsLost, useOpportunityActivities |
|
||||||
|
| **APIs** | Opportunities API, Activities API |
|
||||||
|
|
||||||
|
**Secciones:**
|
||||||
|
- Valor y probabilidad
|
||||||
|
- Descripción
|
||||||
|
- Timeline de actividades
|
||||||
|
- Status (Open/Won/Lost)
|
||||||
|
- Contacto
|
||||||
|
- Timeline de fechas
|
||||||
|
|
||||||
|
**Acciones:**
|
||||||
|
- Editar oportunidad
|
||||||
|
- Eliminar
|
||||||
|
- Marcar como Won
|
||||||
|
- Marcar como Lost
|
||||||
|
- Agregar actividad
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.6 ActivitiesPage
|
||||||
|
|
||||||
|
**Ruta:** `/dashboard/sales/activities`
|
||||||
|
**Archivo:** `src/pages/sales/activities/index.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Gestión de actividades de ventas |
|
||||||
|
| **Layout** | DashboardLayout |
|
||||||
|
| **Guard** | ProtectedRoute |
|
||||||
|
| **Componentes** | ActivityForm |
|
||||||
|
| **Hooks** | useActivities, useUpcomingActivities, useOverdueActivities, useActivityStats |
|
||||||
|
| **APIs** | Activities API |
|
||||||
|
|
||||||
|
**Tipos de actividad:**
|
||||||
|
- Call
|
||||||
|
- Meeting
|
||||||
|
- Task
|
||||||
|
- Email
|
||||||
|
- Note
|
||||||
|
|
||||||
|
**Estados:**
|
||||||
|
- Pending
|
||||||
|
- Completed
|
||||||
|
- Cancelled
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Commissions Pages
|
||||||
|
|
||||||
|
### 4.1 CommissionsPage (Dashboard)
|
||||||
|
|
||||||
|
**Ruta:** `/dashboard/commissions`
|
||||||
|
**Archivo:** `src/pages/commissions/index.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Dashboard de comisiones |
|
||||||
|
| **Layout** | DashboardLayout |
|
||||||
|
| **Guard** | ProtectedRoute |
|
||||||
|
| **Componentes** | CommissionsDashboard |
|
||||||
|
| **Hooks** | useCommissionsSummary, useTopEarners, useEarningsByPeriod |
|
||||||
|
| **APIs** | Commissions Dashboard API |
|
||||||
|
|
||||||
|
**Métricas:**
|
||||||
|
- Total comisiones
|
||||||
|
- Pendientes
|
||||||
|
- Aprobadas
|
||||||
|
- Top earners
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4.2 SchemesPage
|
||||||
|
|
||||||
|
**Ruta:** `/dashboard/commissions/schemes`
|
||||||
|
**Archivo:** `src/pages/commissions/schemes/index.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Configuración de esquemas de comisiones |
|
||||||
|
| **Layout** | DashboardLayout |
|
||||||
|
| **Guard** | ProtectedRoute |
|
||||||
|
| **Componentes** | SchemesList, SchemeForm |
|
||||||
|
| **Hooks** | useSchemes |
|
||||||
|
| **APIs** | Schemes API |
|
||||||
|
|
||||||
|
**Tipos de esquema:**
|
||||||
|
- Percentage
|
||||||
|
- Fixed
|
||||||
|
- Tiered
|
||||||
|
|
||||||
|
**Filtros:**
|
||||||
|
- Type
|
||||||
|
- Status (active/inactive)
|
||||||
|
- Search
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4.3 EntriesPage
|
||||||
|
|
||||||
|
**Ruta:** `/dashboard/commissions/entries`
|
||||||
|
**Archivo:** `src/pages/commissions/entries/index.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Gestión de entradas de comisiones |
|
||||||
|
| **Layout** | DashboardLayout |
|
||||||
|
| **Guard** | ProtectedRoute |
|
||||||
|
| **Componentes** | EntriesList |
|
||||||
|
| **Hooks** | useEntries, useBulkApprove, useBulkReject |
|
||||||
|
| **APIs** | Entries API |
|
||||||
|
|
||||||
|
**Estados:**
|
||||||
|
- Pending
|
||||||
|
- Approved
|
||||||
|
- Rejected
|
||||||
|
- Paid
|
||||||
|
- Cancelled
|
||||||
|
|
||||||
|
**Acciones bulk:**
|
||||||
|
- Aprobar seleccionados
|
||||||
|
- Rechazar seleccionados
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4.4 PeriodsPage
|
||||||
|
|
||||||
|
**Ruta:** `/dashboard/commissions/periods`
|
||||||
|
**Archivo:** `src/pages/commissions/periods/index.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Gestión de períodos de pago |
|
||||||
|
| **Layout** | DashboardLayout |
|
||||||
|
| **Guard** | ProtectedRoute |
|
||||||
|
| **Componentes** | PeriodManager |
|
||||||
|
| **Hooks** | usePeriods, useCreatePeriod |
|
||||||
|
| **APIs** | Periods API |
|
||||||
|
|
||||||
|
**Estados de período:**
|
||||||
|
- Open
|
||||||
|
- Closed
|
||||||
|
- Processing
|
||||||
|
- Paid
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4.5 MyEarningsPage
|
||||||
|
|
||||||
|
**Ruta:** `/dashboard/commissions/my-earnings`
|
||||||
|
**Archivo:** `src/pages/commissions/my-earnings/index.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Vista de ganancias del usuario actual |
|
||||||
|
| **Layout** | DashboardLayout |
|
||||||
|
| **Guard** | ProtectedRoute |
|
||||||
|
| **Componentes** | EarningsCard, EntryStatusBadge |
|
||||||
|
| **Hooks** | useMyEarnings |
|
||||||
|
| **APIs** | Dashboard API (my earnings) |
|
||||||
|
|
||||||
|
**Secciones:**
|
||||||
|
- Total earnings
|
||||||
|
- Pending
|
||||||
|
- Approved
|
||||||
|
- Current period earnings
|
||||||
|
- Recent commissions table
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Settings Pages
|
||||||
|
|
||||||
|
### 5.1 SettingsPage
|
||||||
|
|
||||||
|
**Ruta:** `/dashboard/settings`
|
||||||
|
**Archivo:** `src/pages/settings/index.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Configuración de cuenta (tabs) |
|
||||||
|
| **Layout** | DashboardLayout |
|
||||||
|
| **Guard** | ProtectedRoute |
|
||||||
|
| **Componentes** | GeneralSettings, NotificationSettings, SecuritySettings |
|
||||||
|
| **Hooks** | useAuth, useCurrentUser |
|
||||||
|
| **APIs** | `usersApi.update()`, `notificationsApi.*`, `authApi.*` |
|
||||||
|
|
||||||
|
**Tabs:**
|
||||||
|
- General (perfil)
|
||||||
|
- Notifications (preferencias)
|
||||||
|
- Security (password, MFA)
|
||||||
|
- AI (configuración AI)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5.2 GeneralSettings
|
||||||
|
|
||||||
|
**Archivo:** `src/pages/settings/GeneralSettings.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Configuración de perfil |
|
||||||
|
| **Componentes** | Form básico |
|
||||||
|
| **Hooks** | useCurrentUser, useForm |
|
||||||
|
|
||||||
|
**Campos:**
|
||||||
|
- First name
|
||||||
|
- Last name
|
||||||
|
- Email (read-only)
|
||||||
|
- Avatar
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5.3 NotificationSettings
|
||||||
|
|
||||||
|
**Archivo:** `src/pages/settings/NotificationSettings.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Preferencias de notificaciones |
|
||||||
|
| **Componentes** | Toggle switches |
|
||||||
|
| **Hooks** | useNotifications |
|
||||||
|
|
||||||
|
**Configuración:**
|
||||||
|
- Email notifications
|
||||||
|
- Push notifications
|
||||||
|
- In-app notifications
|
||||||
|
- Tipos de eventos
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5.4 SecuritySettings
|
||||||
|
|
||||||
|
**Archivo:** `src/pages/settings/SecuritySettings.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Seguridad de la cuenta |
|
||||||
|
| **Componentes** | Form de password |
|
||||||
|
| **Hooks** | useAuth |
|
||||||
|
|
||||||
|
**Secciones:**
|
||||||
|
- Cambiar contraseña
|
||||||
|
- Sesiones activas
|
||||||
|
- MFA (futuro)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Superadmin Pages
|
||||||
|
|
||||||
|
### 6.1 TenantsPage
|
||||||
|
|
||||||
|
**Ruta:** `/superadmin/tenants`
|
||||||
|
**Archivo:** `src/pages/superadmin/TenantsPage.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Listado de todos los tenants |
|
||||||
|
| **Layout** | DashboardLayout |
|
||||||
|
| **Guard** | SuperadminRoute |
|
||||||
|
| **Componentes** | TenantTable |
|
||||||
|
| **Hooks** | useSuperadmin |
|
||||||
|
| **APIs** | `superadminApi.listTenants()` |
|
||||||
|
|
||||||
|
**Filtros:**
|
||||||
|
- Search
|
||||||
|
- Status
|
||||||
|
- Plan
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6.2 TenantDetailPage
|
||||||
|
|
||||||
|
**Ruta:** `/superadmin/tenants/:id`
|
||||||
|
**Archivo:** `src/pages/superadmin/TenantDetailPage.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Detalle de un tenant específico |
|
||||||
|
| **Layout** | DashboardLayout |
|
||||||
|
| **Guard** | SuperadminRoute |
|
||||||
|
| **Componentes** | TenantForm, UserTable |
|
||||||
|
| **Hooks** | useSuperadmin |
|
||||||
|
| **APIs** | `superadminApi.getTenant()`, `superadminApi.getTenantUsers()` |
|
||||||
|
|
||||||
|
**Secciones:**
|
||||||
|
- Información del tenant
|
||||||
|
- Usuarios del tenant
|
||||||
|
- Suscripción
|
||||||
|
- Métricas de uso
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6.3 MetricsPage
|
||||||
|
|
||||||
|
**Ruta:** `/superadmin/metrics`
|
||||||
|
**Archivo:** `src/pages/superadmin/MetricsPage.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Métricas globales del sistema |
|
||||||
|
| **Layout** | DashboardLayout |
|
||||||
|
| **Guard** | SuperadminRoute |
|
||||||
|
| **Componentes** | MetricCard, TrendChart |
|
||||||
|
| **Hooks** | useSuperadmin |
|
||||||
|
| **APIs** | `superadminApi.getMetricsSummary()`, `superadminApi.getTenantGrowth()` |
|
||||||
|
|
||||||
|
**Métricas:**
|
||||||
|
- Total tenants
|
||||||
|
- Total users
|
||||||
|
- MRR
|
||||||
|
- Tenant growth chart
|
||||||
|
- Plan distribution
|
||||||
|
- Top tenants
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Onboarding Pages
|
||||||
|
|
||||||
|
### 7.1 OnboardingPage
|
||||||
|
|
||||||
|
**Ruta:** `/onboarding`
|
||||||
|
**Archivo:** `src/pages/onboarding/OnboardingPage.tsx`
|
||||||
|
|
||||||
|
| Aspecto | Detalle |
|
||||||
|
|---------|---------|
|
||||||
|
| **Propósito** | Wizard de onboarding para nuevos tenants |
|
||||||
|
| **Layout** | Propio (sin DashboardLayout) |
|
||||||
|
| **Guard** | ProtectedRoute |
|
||||||
|
| **Componentes** | WizardProgress, CompanyStep, PlanStep, InviteStep, CompleteStep |
|
||||||
|
| **Hooks** | useOnboarding, useOnboardingStatus |
|
||||||
|
| **APIs** | Onboarding API |
|
||||||
|
|
||||||
|
**Pasos:**
|
||||||
|
1. **CompanyStep** - Información de la empresa (nombre, logo)
|
||||||
|
2. **PlanStep** - Selección de plan
|
||||||
|
3. **InviteStep** - Invitar miembros del equipo
|
||||||
|
4. **CompleteStep** - Confirmación y redirección
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resumen de Estados Comunes
|
||||||
|
|
||||||
|
### Loading State
|
||||||
|
```tsx
|
||||||
|
<div className="flex items-center justify-center h-64">
|
||||||
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600" />
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Empty State
|
||||||
|
```tsx
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<p className="text-gray-500">No data found</p>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error State
|
||||||
|
```tsx
|
||||||
|
<div className="bg-red-50 p-4 rounded-lg">
|
||||||
|
<p className="text-red-600">{error.message}</p>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resumen Cuantitativo
|
||||||
|
|
||||||
|
| Categoría | Cantidad |
|
||||||
|
|-----------|----------|
|
||||||
|
| Auth Pages | 3 |
|
||||||
|
| Dashboard Core | 9 |
|
||||||
|
| Sales Pages | 6 |
|
||||||
|
| Commissions Pages | 5 |
|
||||||
|
| Settings Pages | 4 |
|
||||||
|
| Superadmin Pages | 3 |
|
||||||
|
| Onboarding Pages | 1 (+4 steps) |
|
||||||
|
| **Total** | **38** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Documentación generada - Template SaaS v1.0.0 - 2026-01-25*
|
||||||
287
docs/05-frontend/FRONTEND-ROUTING.md
Normal file
287
docs/05-frontend/FRONTEND-ROUTING.md
Normal file
@ -0,0 +1,287 @@
|
|||||||
|
# Frontend Routing - Template SaaS
|
||||||
|
|
||||||
|
**Versión:** 1.0.0
|
||||||
|
**Actualizado:** 2026-01-25
|
||||||
|
**Router:** React Router DOM v7.1.1
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Arquitectura de Routing
|
||||||
|
|
||||||
|
El sistema de routing está basado en **React Router v6/7** con guards de protección para controlar el acceso a las rutas según el estado de autenticación y rol del usuario.
|
||||||
|
|
||||||
|
### Archivo Principal
|
||||||
|
`apps/frontend/src/router/index.tsx`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Guards de Protección
|
||||||
|
|
||||||
|
| Guard | Descripción | Redirección |
|
||||||
|
|-------|-------------|-------------|
|
||||||
|
| **GuestRoute** | Solo usuarios no autenticados | → `/dashboard` si está autenticado |
|
||||||
|
| **ProtectedRoute** | Requiere autenticación | → `/auth/login` si no está autenticado |
|
||||||
|
| **SuperadminRoute** | Requiere rol `superadmin` | → `/dashboard` si no tiene rol |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Mapa de Rutas Completo
|
||||||
|
|
||||||
|
### 1. Rutas Públicas
|
||||||
|
|
||||||
|
| Ruta | Componente | Guard | Descripción |
|
||||||
|
|------|------------|-------|-------------|
|
||||||
|
| `/` | Navigate | - | Redirect a `/auth/login` |
|
||||||
|
|
||||||
|
### 2. Rutas de Autenticación (`/auth`)
|
||||||
|
|
||||||
|
**Layout:** `AuthLayout`
|
||||||
|
**Guard:** `GuestRoute`
|
||||||
|
|
||||||
|
| Ruta | Componente | Descripción |
|
||||||
|
|------|------------|-------------|
|
||||||
|
| `/auth/login` | `LoginPage` | Formulario de inicio de sesión |
|
||||||
|
| `/auth/register` | `RegisterPage` | Formulario de registro |
|
||||||
|
| `/auth/forgot-password` | `ForgotPasswordPage` | Recuperación de contraseña |
|
||||||
|
|
||||||
|
### 3. Rutas de Dashboard (`/dashboard`)
|
||||||
|
|
||||||
|
**Layout:** `DashboardLayout`
|
||||||
|
**Guard:** `ProtectedRoute`
|
||||||
|
|
||||||
|
#### 3.1 Rutas Core
|
||||||
|
|
||||||
|
| Ruta | Componente | Descripción |
|
||||||
|
|------|------------|-------------|
|
||||||
|
| `/dashboard` | `DashboardPage` | Dashboard principal con métricas |
|
||||||
|
| `/dashboard/settings` | `SettingsPage` | Configuración de cuenta |
|
||||||
|
| `/dashboard/billing` | `BillingPage` | Gestión de suscripción (Stripe) |
|
||||||
|
| `/dashboard/users` | `UsersPage` | Gestión de usuarios del tenant |
|
||||||
|
| `/dashboard/ai` | `AIPage` | Panel de control AI/LLM |
|
||||||
|
| `/dashboard/storage` | `StoragePage` | Gestión de archivos |
|
||||||
|
| `/dashboard/webhooks` | `WebhooksPage` | Configuración de webhooks |
|
||||||
|
| `/dashboard/audit` | `AuditLogsPage` | Registro de auditoría |
|
||||||
|
| `/dashboard/feature-flags` | `FeatureFlagsPage` | Feature toggles |
|
||||||
|
| `/dashboard/whatsapp` | `WhatsAppSettings` | Config WhatsApp Business |
|
||||||
|
|
||||||
|
#### 3.2 Rutas de Sales (`/dashboard/sales`)
|
||||||
|
|
||||||
|
| Ruta | Componente | Parámetros | Descripción |
|
||||||
|
|------|------------|------------|-------------|
|
||||||
|
| `/dashboard/sales` | `SalesPage` | - | Dashboard de ventas |
|
||||||
|
| `/dashboard/sales/leads` | `LeadsPage` | - | Listado de leads |
|
||||||
|
| `/dashboard/sales/leads/:id` | `LeadDetailPage` | `id: string` | Detalle de lead |
|
||||||
|
| `/dashboard/sales/opportunities` | `OpportunitiesPage` | - | Pipeline de oportunidades |
|
||||||
|
| `/dashboard/sales/opportunities/:id` | `OpportunityDetailPage` | `id: string` | Detalle de oportunidad |
|
||||||
|
| `/dashboard/sales/activities` | `ActivitiesPage` | - | Actividades de ventas |
|
||||||
|
|
||||||
|
#### 3.3 Rutas de Commissions (`/dashboard/commissions`)
|
||||||
|
|
||||||
|
| Ruta | Componente | Parámetros | Descripción |
|
||||||
|
|------|------------|------------|-------------|
|
||||||
|
| `/dashboard/commissions` | `CommissionsPage` | - | Dashboard de comisiones |
|
||||||
|
| `/dashboard/commissions/schemes` | `SchemesPage` | - | Esquemas de comisiones |
|
||||||
|
| `/dashboard/commissions/entries` | `EntriesPage` | - | Entradas de comisiones |
|
||||||
|
| `/dashboard/commissions/periods` | `PeriodsPage` | - | Períodos de pago |
|
||||||
|
| `/dashboard/commissions/my-earnings` | `MyEarningsPage` | - | Mis ganancias |
|
||||||
|
|
||||||
|
### 4. Rutas de Superadmin (`/superadmin`)
|
||||||
|
|
||||||
|
**Layout:** `DashboardLayout`
|
||||||
|
**Guard:** `SuperadminRoute`
|
||||||
|
|
||||||
|
| Ruta | Componente | Parámetros | Descripción |
|
||||||
|
|------|------------|------------|-------------|
|
||||||
|
| `/superadmin` | Navigate | - | Redirect a `/superadmin/tenants` |
|
||||||
|
| `/superadmin/tenants` | `TenantsPage` | - | Listado de tenants |
|
||||||
|
| `/superadmin/tenants/:id` | `TenantDetailPage` | `id: string` | Detalle de tenant |
|
||||||
|
| `/superadmin/metrics` | `MetricsPage` | - | Métricas del sistema |
|
||||||
|
|
||||||
|
### 5. Ruta de Onboarding
|
||||||
|
|
||||||
|
| Ruta | Componente | Guard | Descripción |
|
||||||
|
|------|------------|-------|-------------|
|
||||||
|
| `/onboarding` | `OnboardingPage` | `ProtectedRoute` | Wizard de onboarding (4 pasos) |
|
||||||
|
|
||||||
|
### 6. Ruta 404 (Catch-All)
|
||||||
|
|
||||||
|
| Ruta | Componente | Descripción |
|
||||||
|
|------|------------|-------------|
|
||||||
|
| `*` | Navigate | Redirect a `/` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Diagrama de Navegación
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐
|
||||||
|
│ / │
|
||||||
|
│ (redirect) │
|
||||||
|
└────────┬────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────────┴──────────────────┐
|
||||||
|
│ │
|
||||||
|
▼ ▼
|
||||||
|
┌─────────────────┐ ┌─────────────────┐
|
||||||
|
│ /auth/* │ │ /dashboard/* │
|
||||||
|
│ (GuestRoute) │ │ (ProtectedRoute)│
|
||||||
|
├─────────────────┤ ├─────────────────┤
|
||||||
|
│ login │◄─────────────────►│ (index) │
|
||||||
|
│ register │ login/logout │ settings │
|
||||||
|
│ forgot-password │ │ billing │
|
||||||
|
└─────────────────┘ │ users │
|
||||||
|
│ ai │
|
||||||
|
│ storage │
|
||||||
|
│ webhooks │
|
||||||
|
│ audit │
|
||||||
|
│ feature-flags │
|
||||||
|
│ whatsapp │
|
||||||
|
│ │
|
||||||
|
│ sales/* │
|
||||||
|
│ ├─ (index) │
|
||||||
|
│ ├─ leads │
|
||||||
|
│ ├─ leads/:id │
|
||||||
|
│ ├─ opportunities│
|
||||||
|
│ ├─ opportunities/:id │
|
||||||
|
│ └─ activities │
|
||||||
|
│ │
|
||||||
|
│ commissions/* │
|
||||||
|
│ ├─ (index) │
|
||||||
|
│ ├─ schemes │
|
||||||
|
│ ├─ entries │
|
||||||
|
│ ├─ periods │
|
||||||
|
│ └─ my-earnings │
|
||||||
|
└────────┬────────┘
|
||||||
|
│
|
||||||
|
│ (superadmin only)
|
||||||
|
▼
|
||||||
|
┌─────────────────┐
|
||||||
|
│ /superadmin/* │
|
||||||
|
│(SuperadminRoute)│
|
||||||
|
├─────────────────┤
|
||||||
|
│ tenants │
|
||||||
|
│ tenants/:id │
|
||||||
|
│ metrics │
|
||||||
|
└─────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Layouts
|
||||||
|
|
||||||
|
### AuthLayout
|
||||||
|
- **Ubicación:** `src/layouts/AuthLayout.tsx`
|
||||||
|
- **Uso:** Rutas de autenticación (`/auth/*`)
|
||||||
|
- **Características:**
|
||||||
|
- Diseño de 2 columnas (branding + formulario)
|
||||||
|
- Responsive para móvil
|
||||||
|
- Sin sidebar ni navegación
|
||||||
|
|
||||||
|
### DashboardLayout
|
||||||
|
- **Ubicación:** `src/layouts/DashboardLayout.tsx`
|
||||||
|
- **Uso:** Dashboard y Superadmin
|
||||||
|
- **Características:**
|
||||||
|
- Sidebar fijo con navegación
|
||||||
|
- Top bar con usuario y notificaciones
|
||||||
|
- Contenido principal con scroll
|
||||||
|
- Menú dinámico según rol
|
||||||
|
- Outlet para rutas hijas
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Flujos de Navegación
|
||||||
|
|
||||||
|
### Flujo de Autenticación
|
||||||
|
```
|
||||||
|
Usuario no autenticado
|
||||||
|
│
|
||||||
|
├─► /dashboard → redirect → /auth/login
|
||||||
|
│
|
||||||
|
└─► /auth/login → login exitoso → /dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flujo de Onboarding
|
||||||
|
```
|
||||||
|
Nuevo usuario (post-registro)
|
||||||
|
│
|
||||||
|
└─► /onboarding
|
||||||
|
│
|
||||||
|
├─ Step 1: CompanyStep (info empresa)
|
||||||
|
├─ Step 2: PlanStep (selección plan)
|
||||||
|
├─ Step 3: InviteStep (invitar usuarios)
|
||||||
|
└─ Step 4: CompleteStep → /dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flujo de Superadmin
|
||||||
|
```
|
||||||
|
Usuario con role=superadmin
|
||||||
|
│
|
||||||
|
├─► /dashboard/* → acceso normal
|
||||||
|
│
|
||||||
|
└─► /superadmin/* → acceso permitido
|
||||||
|
│
|
||||||
|
└─ /superadmin/tenants/:id → detalle tenant
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Parámetros Dinámicos
|
||||||
|
|
||||||
|
| Ruta | Parámetro | Tipo | Descripción |
|
||||||
|
|------|-----------|------|-------------|
|
||||||
|
| `/dashboard/sales/leads/:id` | `id` | `string` (UUID) | ID del lead |
|
||||||
|
| `/dashboard/sales/opportunities/:id` | `id` | `string` (UUID) | ID de la oportunidad |
|
||||||
|
| `/superadmin/tenants/:id` | `id` | `string` (UUID) | ID del tenant |
|
||||||
|
|
||||||
|
### Uso en Componentes
|
||||||
|
```tsx
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
function LeadDetailPage() {
|
||||||
|
const { id } = useParams<{ id: string }>();
|
||||||
|
// id contiene el UUID del lead
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Estado de Autenticación
|
||||||
|
|
||||||
|
### Store: `auth.store.ts`
|
||||||
|
```typescript
|
||||||
|
interface User {
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
first_name: string;
|
||||||
|
last_name: string;
|
||||||
|
role: string; // 'user' | 'admin' | 'superadmin'
|
||||||
|
tenant_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AuthState {
|
||||||
|
user: User | null;
|
||||||
|
accessToken: string | null;
|
||||||
|
refreshToken: string | null;
|
||||||
|
isAuthenticated: boolean;
|
||||||
|
isLoading: boolean;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resumen de Rutas
|
||||||
|
|
||||||
|
| Portal | Cantidad | Guard |
|
||||||
|
|--------|----------|-------|
|
||||||
|
| Auth | 3 rutas | GuestRoute |
|
||||||
|
| Dashboard Core | 10 rutas | ProtectedRoute |
|
||||||
|
| Dashboard Sales | 6 rutas | ProtectedRoute |
|
||||||
|
| Dashboard Commissions | 5 rutas | ProtectedRoute |
|
||||||
|
| Superadmin | 3 rutas | SuperadminRoute |
|
||||||
|
| Onboarding | 1 ruta | ProtectedRoute |
|
||||||
|
| **Total** | **28 rutas** | - |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Documentación generada - Template SaaS v1.0.0*
|
||||||
@ -1,16 +1,17 @@
|
|||||||
---
|
---
|
||||||
# FRONTEND INVENTORY - Template SaaS
|
# FRONTEND INVENTORY - Template SaaS
|
||||||
# Version: 4.0.0
|
# Version: 4.1.0
|
||||||
# Ultima actualizacion: 2026-01-24
|
# Ultima actualizacion: 2026-01-25
|
||||||
# Nota: AUDITORIA DE COHERENCIA - Sincronizado con codigo real
|
# Nota: AUDITORIA DE COHERENCIA - Sincronizado con codigo real
|
||||||
# CORRECCION 2026-01-24: Sales (SAAS-018) y Commissions (SAAS-020) SI implementados en frontend
|
# CORRECCION 2026-01-24: Sales (SAAS-018) y Commissions (SAAS-020) SI implementados en frontend
|
||||||
|
# ACTUALIZACION 2026-01-25: Rutas Sales/Commissions agregadas al router, authStore completado
|
||||||
# Verificado: 6 paginas Sales, 5 paginas Commissions, APIs y hooks completos
|
# Verificado: 6 paginas Sales, 5 paginas Commissions, APIs y hooks completos
|
||||||
|
|
||||||
metadata:
|
metadata:
|
||||||
proyecto: "template-saas"
|
proyecto: "template-saas"
|
||||||
tipo: "FRONTEND"
|
tipo: "FRONTEND"
|
||||||
version: "4.0.0"
|
version: "4.1.0"
|
||||||
updated: "2026-01-24"
|
updated: "2026-01-25"
|
||||||
framework: "React 19.0.0 + Vite 6.0.6"
|
framework: "React 19.0.0 + Vite 6.0.6"
|
||||||
styling: "Tailwind CSS 3.4.17"
|
styling: "Tailwind CSS 3.4.17"
|
||||||
state: "Zustand 5.0.2"
|
state: "Zustand 5.0.2"
|
||||||
@ -219,18 +220,19 @@ shared:
|
|||||||
implementados:
|
implementados:
|
||||||
- nombre: "authStore"
|
- nombre: "authStore"
|
||||||
archivo: "auth.store.ts"
|
archivo: "auth.store.ts"
|
||||||
estado: "parcial"
|
estado: "completado"
|
||||||
actions_implementadas:
|
actions_implementadas:
|
||||||
- login
|
- login
|
||||||
- logout
|
- logout
|
||||||
- setUser
|
- setUser
|
||||||
- setTokens
|
- setTokens
|
||||||
- setLoading
|
- setLoading
|
||||||
actions_faltantes:
|
- refreshTokens
|
||||||
- refreshToken
|
|
||||||
- updateProfile
|
- updateProfile
|
||||||
|
actions_faltantes: []
|
||||||
usa_persist: true
|
usa_persist: true
|
||||||
storage_key: "auth-storage"
|
storage_key: "auth-storage"
|
||||||
|
nota: "COMPLETADO 2026-01-25: refreshTokens y updateProfile implementados"
|
||||||
- nombre: "uiStore"
|
- nombre: "uiStore"
|
||||||
archivo: "ui.store.ts"
|
archivo: "ui.store.ts"
|
||||||
estado: "completado"
|
estado: "completado"
|
||||||
@ -448,10 +450,13 @@ gaps_identificados:
|
|||||||
- "4 stores Zustand adicionales pendientes"
|
- "4 stores Zustand adicionales pendientes"
|
||||||
medios:
|
medios:
|
||||||
- "Componentes Forms no implementados como wrappers"
|
- "Componentes Forms no implementados como wrappers"
|
||||||
- "authStore incompleto (falta refreshToken, updateProfile)"
|
|
||||||
resueltos_2026_01_24:
|
resueltos_2026_01_24:
|
||||||
- "Portal Sales (SAAS-018): Ahora completado (6 paginas)"
|
- "Portal Sales (SAAS-018): Ahora completado (6 paginas)"
|
||||||
- "Portal Commissions (SAAS-020): Ahora completado (5 paginas)"
|
- "Portal Commissions (SAAS-020): Ahora completado (5 paginas)"
|
||||||
|
resueltos_2026_01_25:
|
||||||
|
- "authStore completado (refreshTokens y updateProfile implementados)"
|
||||||
|
- "Rutas Sales/Commissions agregadas al router (antes existian paginas pero no rutas)"
|
||||||
|
- "Documentacion FRONTEND-ROUTING.md creada"
|
||||||
|
|
||||||
dependencias_npm:
|
dependencias_npm:
|
||||||
core:
|
core:
|
||||||
@ -477,9 +482,13 @@ dependencias_npm:
|
|||||||
notifications:
|
notifications:
|
||||||
- "socket.io-client"
|
- "socket.io-client"
|
||||||
|
|
||||||
ultima_actualizacion: "2026-01-24"
|
ultima_actualizacion: "2026-01-25"
|
||||||
actualizado_por: "Claude Opus 4.5 (Auditoria de Coherencia)"
|
actualizado_por: "Claude Opus 4.5 (Alineacion Doc-Codigo)"
|
||||||
historial_cambios:
|
historial_cambios:
|
||||||
|
- fecha: "2026-01-25"
|
||||||
|
tipo: "alineacion"
|
||||||
|
descripcion: "Rutas Sales/Commissions agregadas al router. authStore completado (refreshTokens, updateProfile). Documentacion FRONTEND-ROUTING.md creada."
|
||||||
|
agente: "Claude Opus 4.5 (Alineacion Doc-Codigo)"
|
||||||
- fecha: "2026-01-24"
|
- fecha: "2026-01-24"
|
||||||
tipo: "correccion_critica"
|
tipo: "correccion_critica"
|
||||||
descripcion: "CORRECCION: Sales (6 paginas) y Commissions (5 paginas) SI implementados. Build de frontend exitoso (2733 modulos). APIs y hooks ya listados correctamente."
|
descripcion: "CORRECCION: Sales (6 paginas) y Commissions (5 paginas) SI implementados. Build de frontend exitoso (2733 modulos). APIs y hooks ya listados correctamente."
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user