CRM System Web Interface Development

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:
Development stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    847
  • image_website-sbh_0.png
    Website development for SBH Partners
    999
  • image_website-_0.png
    Website development for Red Pear
    451

CRM System Development (Web Interface)

CRM system in most cases — not "customer relationship management" in marketing sense, but specific tool: task queue, customer card, deal pipeline, communication history. Web interface development task — implement exactly the feature set specific business needs, without extra modules, settings clutter, and license limits.

What Includes in Typical CRM Interface

Minimal entity set for most B2B companies:

  • Contacts — individuals with interaction history
  • Companies — legal entities to which contacts linked
  • Deals — potential and active sales with statuses
  • Tasks — assignments with deadlines linked to deals/contacts
  • Activities — calls, emails, meetings (communication log)
  • Pipeline — visual Kanban or Pipeline with status columns

Additionally depending on specifics: price lists and commercial proposals, telephony integration, mailing module, manager reports.

Tech Stack

For CRM web interface optimal SPA or SSR app with reactive UI:

Backend:

  • Laravel / Node.js (NestJS) as API
  • PostgreSQL — main database
  • Redis — cache and event queues (calls, notifications)
  • WebSocket (Laravel Echo + Pusher / Socket.io) — real-time updates

Frontend:

  • React + TypeScript
  • React Query for server state
  • Zustand or Redux Toolkit for global UI state
  • React Hook Form + Zod for forms
  • TanStack Table for tables with filtering/sorting
  • @dnd-kit for drag-and-drop pipeline

Database Structure

CREATE TABLE contacts (
    id          BIGSERIAL PRIMARY KEY,
    company_id  BIGINT REFERENCES companies(id),
    name        VARCHAR(255) NOT NULL,
    email       VARCHAR(255),
    phone       VARCHAR(50),
    source      VARCHAR(64),      -- where came from
    responsible_id BIGINT REFERENCES users(id),
    created_at  TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE deals (
    id           BIGSERIAL PRIMARY KEY,
    contact_id   BIGINT REFERENCES contacts(id),
    company_id   BIGINT REFERENCES companies(id),
    title        VARCHAR(255) NOT NULL,
    amount       DECIMAL(14,2),
    currency     CHAR(3) DEFAULT 'RUB',
    stage_id     BIGINT REFERENCES pipeline_stages(id),
    responsible_id BIGINT REFERENCES users(id),
    closed_at    DATE,
    created_at   TIMESTAMPTZ DEFAULT NOW(),
    updated_at   TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE pipeline_stages (
    id           BIGSERIAL PRIMARY KEY,
    pipeline_id  BIGINT REFERENCES pipelines(id),
    name         VARCHAR(128) NOT NULL,
    sort_order   INT DEFAULT 0,
    is_won       BOOLEAN DEFAULT FALSE,
    is_lost      BOOLEAN DEFAULT FALSE
);

CREATE TABLE activities (
    id           BIGSERIAL PRIMARY KEY,
    entity_type  VARCHAR(32) NOT NULL, -- 'contact', 'deal', 'company'
    entity_id    BIGINT NOT NULL,
    type         VARCHAR(32) NOT NULL, -- 'call', 'email', 'meeting', 'note'
    body         TEXT,
    user_id      BIGINT REFERENCES users(id),
    happened_at  TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX ON activities(entity_type, entity_id);

API Design

RESTful JSON API with resource-oriented structure:

GET    /api/deals?stage_id=2&responsible_id=5&page=1&per_page=50
POST   /api/deals
PATCH  /api/deals/{id}
DELETE /api/deals/{id}

POST   /api/deals/{id}/move   # stage change
POST   /api/activities        # add activity to any entity
GET    /api/contacts/{id}/timeline  # interaction chronology

PATCH response example on stage change:

{
  "id": 1042,
  "stage_id": 4,
  "stage": { "id": 4, "name": "Negotiation" },
  "updated_at": "2025-03-15T14:22:00Z",
  "activity": {
    "id": 3891,
    "type": "stage_change",
    "body": "Stage changed: Qualification → Negotiation",
    "user_id": 12
  }
}

Pipeline: Drag-and-Drop Kanban

import { DndContext, DragEndEvent, closestCenter } from '@dnd-kit/core';
import { SortableContext } from '@dnd-kit/sortable';

const Pipeline: React.FC<{ stages: Stage[]; deals: Deal[] }> = ({ stages, deals }) => {
    const moveDeal = useMutation({
        mutationFn: ({ dealId, stageId }: { dealId: number; stageId: number }) =>
            api.patch(`/deals/${dealId}/move`, { stage_id: stageId }),
        onMutate: async ({ dealId, stageId }) => {
            // Optimistic update
            await queryClient.cancelQueries({ queryKey: ['deals'] });
            const prev = queryClient.getQueryData(['deals']);
            queryClient.setQueryData(['deals'], (old: Deal[]) =>
                old.map(d => d.id === dealId ? { ...d, stage_id: stageId } : d)
            );
            return { prev };
        },
        onError: (_, __, context) => {
            queryClient.setQueryData(['deals'], context?.prev);
        },
    });

    const onDragEnd = (event: DragEndEvent) => {
        const { active, over } = event;
        if (!over || active.id === over.id) return;
        moveDeal.mutate({ dealId: Number(active.id), stageId: Number(over.id) });
    };

    return (
        <DndContext collisionDetection={closestCenter} onDragEnd={onDragEnd}>
            <div className="flex gap-4 overflow-x-auto p-4">
                {stages.map(stage => (
                    <KanbanColumn
                        key={stage.id}
                        stage={stage}
                        deals={deals.filter(d => d.stage_id === stage.id)}
                    />
                ))}
            </div>
        </DndContext>
    );
};

Optimistic update important — user sees card on new position instantly, without waiting for server response.

Activity Timeline

Interaction chronology — key CRM part. Implements as polymorphic feed:

type ActivityItem =
    | { type: 'call'; duration: number; result: string }
    | { type: 'email'; subject: string; direction: 'in' | 'out' }
    | { type: 'note'; body: string }
    | { type: 'stage_change'; from: string; to: string };

const ActivityFeed: React.FC<{ entityType: string; entityId: number }> = (props) => {
    const { data } = useQuery({
        queryKey: ['timeline', props.entityType, props.entityId],
        queryFn: () => api.get(`/${props.entityType}s/${props.entityId}/timeline`),
    });

    return (
        <div className="space-y-3">
            <AddActivityForm entityType={props.entityType} entityId={props.entityId} />
            {data?.items.map(item => (
                <ActivityCard key={item.id} item={item} />
            ))}
        </div>
    );
};

Access Rights

CRM requires granular rights: manager sees only own clients, lead — all in own department. Implements via Policy classes:

// Laravel Policy
class DealPolicy {
    public function viewAny(User $user): bool {
        return $user->hasPermission('deals.view');
    }

    public function view(User $user, Deal $deal): bool {
        if ($user->hasRole('admin')) return true;
        if ($user->hasRole('team_lead')) {
            return $deal->responsible->team_id === $user->team_id;
        }
        return $deal->responsible_id === $user->id;
    }

    public function update(User $user, Deal $deal): bool {
        return $user->hasRole('admin') || $deal->responsible_id === $user->id;
    }
}

Scope at Eloquent level:

class Deal extends Model {
    public function scopeVisibleTo(Builder $query, User $user): Builder {
        if ($user->hasRole('admin')) return $query;
        if ($user->hasRole('team_lead')) {
            return $query->whereHas('responsible', fn($q) =>
                $q->where('team_id', $user->team_id)
            );
        }
        return $query->where('responsible_id', $user->id);
    }
}

Real-time Notifications

When deal assigned to other manager or task deadline approaching — notification arrives without page refresh:

// Frontend: channel subscription
import Echo from 'laravel-echo';

const echo = new Echo({ broadcaster: 'pusher', ... });

echo.private(`user.${currentUser.id}`).listen('DealAssigned', (event) => {
    toast.info(`Deal assigned: ${event.deal.title}`);
    queryClient.invalidateQueries({ queryKey: ['deals'] });
});
// Backend: event
class DealAssigned implements ShouldBroadcast {
    public function broadcastOn(): PrivateChannel {
        return new PrivateChannel('user.' . $this->deal->responsible_id);
    }
}

Implementation Timeline

MVP with pipeline, contact/deal cards, tasks, basic analytics: 4–6 weeks. Adding integrations (telephony, email, messengers), advanced reports, roles, multilingual: plus 3–4 weeks. Mobile version (PWA or React Native): plus 3–4 weeks.

Most time spent not on code, but business logic: required fields, allowed stage transitions, data visibility, critical notifications.