Skip to content

Phone Line Filter

The phone line filter adds a dropdown to the messaging sidebar that lets you filter contacts by phone line. It’s permission-gated — only users with the FilterByPhoneLine collection permission see the filter control.

The filter works across all messaging tabs (All, Needs Reply, Follow-up) and persists via URL query parameter.

The filter requires the Message::Collection::FilterByPhoneLine permission. Grant it through Settings → Roles → Message → Collection permissions.

src/bases/auth/types/resources.rs
Message {
Collection { List, Create, ViewAttachment, UploadAttachment, FilterByPhoneLine }
}

The permission catalog entry in src/mods/settings/types/permission_catalog_type.rs registers it as "Filter messaging by phone line" for the role editor UI.

GET /api/messages/phone-lines-for-filter returns available phone lines for the current organization.

Handler: get_messaging_phone_lines_api() in src/mods/messaging/api/get_messaging_phone_lines_api.rs

Authorization: Returns 403 if the user lacks FilterByPhoneLine permission.

Response:

[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"display_name": "Main Number (+17863058649)"
}
]

The endpoint calls the shared fetch_phone_line_options() service, which queries phone_number filtered by organization_id, ordered by friendly_name.

Three items were extracted from the call module to src/shared/ for reuse across call and messaging:

ComponentFilePurpose
PhoneLineOptionsrc/shared/types/phone_line_option_type.rsType with id: Uuid and display_name: String
PhoneLineSelectsrc/shared/components/phone_line_select_component.rsDropdown component with text and icon modes
fetch_phone_line_options()src/shared/utils/fetch_phone_line_options.rsDB query returning Vec<PhoneLineOption> for an org
#[component]
pub fn PhoneLineSelect(
phone_lines: Vec<PhoneLineOption>,
value: Option<Uuid>,
on_change: EventHandler<Option<Uuid>>,
#[props(default)] trigger_class: String,
#[props(default)] list_class: String,
#[props(default = "bottom".to_string())] list_side: String,
icon: Option<Element>, // When set, renders icon-only mode
)

Pass icon for a compact trigger (used in the messaging sidebar). Omit it for a full-width dropdown (used in call filters).

Two filter structs gained a phone_number_id field:

src/mods/contact/types/contact_filter_type.rs
#[serde(default)]
pub phone_number_id: Option<Uuid>,
src/mods/messaging/types/unanswered_contact_type.rs
pub phone_number_id: Option<Uuid>,

The filter state maps to the pnid query parameter in ContactFilterQuery:

src/mods/contact/types/contact_filter_query_type.rs
#[serde(default, skip_serializing_if = "String::is_empty")]
pub pnid: String,

Selecting a phone line navigates to /messaging?pnid=<uuid>. Selecting “All lines” clears the parameter. The filter restores on page reload.

All tabapply_contact_filters_service.rs uses a subquery to include contacts with at least one message on the selected line:

if let Some(phone_number_id) = filter.phone_number_id {
let msg_subquery = schemas::message::Entity::find()
.select_only()
.column(schemas::message::Column::ContactId)
.filter(schemas::message::Column::PhoneNumberId.eq(phone_number_id))
.filter(schemas::message::Column::OrganizationId.eq(org_id))
.into_query();
condition = condition.add(
schemas::contact::Column::Id.in_subquery(msg_subquery),
);
}

Needs Reply / Follow-up tabsget_unanswered_contacts_service.rs appends a WHERE phone_number_id = $N clause to the raw SQL query.

A composite index supports the phone line filter subquery:

-- migration/src/m20260416_120000_message_add_phone_number_id_org_index.rs
CREATE INDEX idx_message_phone_number_id_org_id
ON message (phone_number_id, organization_id);

In MessagingContactList, the component checks the permission and conditionally fetches phone lines:

let can_filter_by_phone_line = session.has_permission(
&Permission::Message(MessagePermission::Collection(
MessageCollectionPermission::FilterByPhoneLine,
)),
);

If permitted, a phone icon (📞) appears in ContactFilterBar. An orange dot badge indicates an active filter. The filter persists across tab switches.

FilePurpose
src/mods/messaging/api/get_messaging_phone_lines_api.rsPermission-gated phone lines endpoint
src/shared/components/phone_line_select_component.rsReusable dropdown (icon + text modes)
src/shared/utils/fetch_phone_line_options.rsShared DB query for phone lines
src/shared/types/phone_line_option_type.rsPhoneLineOption type definition
src/mods/contact/types/contact_filter_type.rsphone_number_id on ContactFilter
src/mods/contact/components/filter_bar/contact_filter_bar_component.rsFilter bar with phone line integration
src/mods/messaging/components/messaging_contact_list_component.rsPermission check and phone line fetch
src/mods/contact/services/apply_contact_filters_service.rsSubquery filter for All tab
src/mods/messaging/services/get_unanswered_contacts_service.rsRaw SQL filter for Needs Reply/Follow-up