Leads

Here you learn how Leads page work.

Backend

On the backend for leads responsible leads module.

your_project/api/src/leads

There you can find dto`s, entities, controller and service. There are three entities responsible for functionality of the leads page: Leads entity (the main one), Lead Status entity, Lead Comments entity, which is separate entity for the comment on the leads.

N.B. How they work read the docs in Businesses part of modules documentation.

Leads are in @ManyToOnerelations with Teams, Lead Statuses, @ManyToMany with Businesses. Leads have connections with Audit Logger Service and it's entity from audit-logger module. Also it have connection with File services from files module.

N.B. About them and how they work you can read the information in the relevant documentation modules.

If talking about lead statuses - then each team will have at minimum 4 default statuses for their leads. Maximum 12. It means that after the team was created - it will receive this 4 statuses. How to change and them you can read in App Settings module.

In Services you will find methods for:

  • Soft delete

  • Multi delete, restore, archive

  • Global search

  • Leads CRUD

  • Lead Status CRUD

  • Photo CRUD

  • Audit logs

  • Comments

  • Statistic (Dashboard page)

N.B. There is a separate module about Global Search.

You can easily navigate in this service just looking at "regions". Each region have their specific methods. For example:

// #region Statuses

This will means that next portion of code are responsive for lead statuses. Or

// #region CRUD and search

This for leads CRUD and search. And so on and so forth. Code example of method from statistic block:

async getLeadsAmountPerPeriod(startDate: string, endDate: string, businessesIds: number[], teamId: string) {
        // if (businessesIds.length > 5) {
        //     throw new BadRequestException('Maximal businesses count is 5');
        // }

        const start = dayjs(startDate).startOf('day');
        const end = dayjs(endDate).endOf('day');

        if (!start.isValid() || !end.isValid()) {
            throw new BadRequestException('Invalid date format');
        }

        const startOfDay = start.startOf('day').toDate();
        const endOfDay = end.endOf('day').toDate();

        const query = this.leadsRepository.createQueryBuilder('lead').leftJoinAndSelect('lead.business', 'business');

        query.andWhere('lead.teamId = :teamId', { teamId });

        query.andWhere('lead.createdAt BETWEEN :startOfDay AND :endOfDay', { startOfDay, endOfDay });

        query.andWhere('business.id IN (:...businessesIds)', { businessesIds });

        const leadsAmount = await query.getCount();

        return leadsAmount;
    }

Code example of lead status method:

    async applyDefaultLeadStatuses(teamId: string) {
        return Promise.all(
            defaultStatuses.map(async status => {
                return await this.leadStatusRepository.save({
                    name: status.name,
                    primaryColor: status.primaryColor,
                    secondaryColor: LeadSecondaryColorMap[status.primaryColor],
                    team: { id: teamId },
                });
            })
        );
    }

Example above is responsible for applying 4 default lead statuses for the new team.

Go there and find all what you reading about!

Frontend

Architecture:

Page file:

your_project/ui/pages/leads/

Components related only to leads page:

your_project/ui/components/leads/

Other components:

your_project/ui/common/components/

Page

On the page you will find:

  • Table with all the information about leads, selection, additional info row, sorting and filtering

  • Selector of active and archive leads

  • Leads search

  • Add lead button

  • Filters and other CRUD buttons which renders in certain conditions.

  • Pagination on the bottom of the page

Search, sorting and filtering results will appear in table. Pagination affects it also.

N.B. How this logic works, you can find a "findAll" and "search" method in backend part. There you can find queryBuilder and how it forms in different conditions.

N.B. Please notice that we use lodash library in frontend and throttling guards in backend. This will prevent too many requests to the server and keep it in safe from sudden fall off.

Take into account the fact we use Ant Design. How work certain things, please read their documents about their components. All works great on mobile devices too. In the main file you can find constants that control render in different breakpoint. i18n responsible for translation. How it work - see relevant documentation in module block.

Filters

Filters are the hardest part of the page. Especially in the frontend part. All because how AntD components renders and how they works with each other.

Lets start from the states:

const [filteredInfo, setFilteredInfo] = useState({});

This is the main state. It is used to form the query to the DB and the result of it we'll see later in the table. Next states is used to control the components work.

const [selectedFilterValue, setSelectedFilterValue] = useState({});
const [sortedInfo, setSortedInfo] = useState({});

const [selectedFilters, setSelectedFilters] = useState({
        states: [],
        statuses: [],
        businesses: [],
    });

const filterOptions = {
        state: leadState.map(state => ({
            text: state.label,
            value: state.value,
        })),
        status: statuses.map(status => ({
            text: status.name,
            value: status.id,
        })),
        business: [
            {
                text: 'No business',
                value: 'No business',
            },
            ...businesses.map(business => ({
                text: business.name,
                value: business.name,
            })),
        ],
    };

Last state controls table filters. filterOptions - controls the filters in separate filter components

const [searchParams, setSearchParams] = useState({
        pageSize: 10,
        currentPage: 1,
        isDeleted: false,
    });
const [searchText, setSearchText] = useState('');

This is search and pagination. In "fetchData" you will find how params forms the request to the server.

On the page you may notice that we have filters in different places, but it renders in certain conditions. Components are different from each other and we need to control them by those states, that's why we need them and they are so many. Some of them use AntD's Popover component, some Dropdown or Select (or our Custom Select). Why table filters and other desktop filter are different? It's because of how AntD components work today and this may be changed in the future. Desktop filters and mobile filters are different too. Mobile filters have drawer which pops up from the bottom up.

Permissions

Each action in this page will check users permission. About them and how they work, please read related block of module.

Last updated