diff --git a/packages/experiments-realm/CRMTask/27c5519e-4b58-4e1f-a158-27074d46c782.json b/packages/experiments-realm/CRMTask/27c5519e-4b58-4e1f-a158-27074d46c782.json index 2823d823b9..0b5d30b95c 100644 --- a/packages/experiments-realm/CRMTask/27c5519e-4b58-4e1f-a158-27074d46c782.json +++ b/packages/experiments-realm/CRMTask/27c5519e-4b58-4e1f-a158-27074d46c782.json @@ -13,8 +13,8 @@ "end": "2025-02-04" }, "priority": { - "index": 2, - "label": "Medium" + "index": 3, + "label": "High" }, "name": "Meet Prospect Customer", "details": null, diff --git a/packages/experiments-realm/CRMTask/792c50f2-7d62-425f-bcc7-8e27852731e2.json b/packages/experiments-realm/CRMTask/792c50f2-7d62-425f-bcc7-8e27852731e2.json new file mode 100644 index 0000000000..489903666f --- /dev/null +++ b/packages/experiments-realm/CRMTask/792c50f2-7d62-425f-bcc7-8e27852731e2.json @@ -0,0 +1,68 @@ +{ + "data": { + "type": "card", + "attributes": { + "status": { + "index": 0, + "label": "Not Started", + "color": null, + "completed": false + }, + "dateRange": { + "start": "2025-01-21", + "end": "2025-01-22" + }, + "priority": { + "index": null, + "label": null + }, + "name": "Today", + "details": null, + "description": null, + "thumbnailURL": null + }, + "relationships": { + "crmApp": { + "links": { + "self": "../CrmApp/4e73712d-2a31-4ffe-9c22-d3de277257a6" + } + }, + "subtasks": { + "links": { + "self": null + } + }, + "assignee": { + "links": { + "self": null + } + }, + "contact": { + "links": { + "self": null + } + }, + "account": { + "links": { + "self": null + } + }, + "deal": { + "links": { + "self": null + } + }, + "tags": { + "links": { + "self": null + } + } + }, + "meta": { + "adoptsFrom": { + "module": "../crm/task", + "name": "CRMTask" + } + } + } +} \ No newline at end of file diff --git a/packages/experiments-realm/crm-app.gts b/packages/experiments-realm/crm-app.gts index da32d5f28f..786e526df1 100644 --- a/packages/experiments-realm/crm-app.gts +++ b/packages/experiments-realm/crm-app.gts @@ -13,6 +13,9 @@ import type Owner from '@ember/owner'; import { tracked } from '@glimmer/tracking'; import { TrackedMap } from 'tracked-built-ins'; import { restartableTask } from 'ember-concurrency'; +import { format, startOfWeek } from 'date-fns'; + +const dateFormat = `yyyy-MM-dd`; import { Component, @@ -31,6 +34,7 @@ import { Query, CardError, SupportedMimeType, + Filter, getCards, } from '@cardstack/runtime-common'; import ContactIcon from '@cardstack/boxel-icons/contact'; @@ -38,6 +42,8 @@ import HeartHandshakeIcon from '@cardstack/boxel-icons/heart-handshake'; import TargetArrowIcon from '@cardstack/boxel-icons/target-arrow'; import CalendarExclamation from '@cardstack/boxel-icons/calendar-exclamation'; import PresentationAnalytics from '@cardstack/boxel-icons/presentation-analytics'; +import ListDetails from '@cardstack/boxel-icons/list-details'; +import { taskStatusValues } from './crm/shared'; import { URGENCY_TAG_VALUES } from './crm/urgency-tag'; import { DEAL_STATUS_VALUES } from './crm/deal-status'; import type { Deal } from './crm/deal'; @@ -125,10 +131,16 @@ const ACCOUNT_FILTERS: LayoutFilter[] = [ const TASK_FILTERS: LayoutFilter[] = [ { displayName: 'All Tasks', - icon: CalendarExclamation, + icon: ListDetails, cardTypeName: 'CRM Task', createNewButtonText: 'Create Task', }, + ...taskStatusValues.map((status) => ({ + displayName: status.label, + icon: status.icon, + cardTypeName: 'CRM Task', + createNewButtonText: 'Create Task', + })), ]; const TABS = [ @@ -307,7 +319,7 @@ class CrmAppTemplate extends Component { //query for tabs and filters get query() { - const { loadAllFilters, activeFilter, activeTabId, searchKey } = this; + const { loadAllFilters, activeFilter, activeTabId } = this; if (!loadAllFilters.isIdle || !activeFilter?.query) return; @@ -341,19 +353,6 @@ class CrmAppTemplate extends Component { ] : []; - const searchFilter = searchKey - ? [ - { - any: [ - { - on: activeFilter.cardRef, - contains: { name: searchKey }, - }, - ], - }, - ] - : []; - return { filter: { on: activeFilter.cardRef, @@ -361,13 +360,65 @@ class CrmAppTemplate extends Component { defaultFilter, ...accountFilter, ...dealFilter, - ...searchFilter, + ...this.searchFilter, + ...this.taskFilter, ], }, sort: this.selectedSort?.sort ?? sortByCardTitleAsc, } as Query; } + get searchFilter(): Filter[] { + return this.searchKey + ? [ + { + any: [ + { + on: this.activeFilter.cardRef, + contains: { name: this.searchKey }, + }, + ], + }, + ] + : []; + } + + get taskFilter(): Filter[] { + let taskFilter: Filter[] = []; + if ( + this.activeTabId === 'Task' && + this.activeFilter.displayName !== 'All Tasks' + ) { + const today = new Date(); + switch (this.activeFilter.displayName) { + case 'Overdue': + const formattedDate = format(today, dateFormat); + taskFilter = [{ range: { 'dateRange.end': { lt: formattedDate } } }]; + break; + case 'Due Today': + const formattedDueToday = format(today, dateFormat); + taskFilter = [{ eq: { 'dateRange.end': formattedDueToday } }]; + break; + case 'Due this week': + const dueThisWeek = startOfWeek(today, { weekStartsOn: 1 }); + const formattedDueThisWeek = format(dueThisWeek, dateFormat); + taskFilter = [ + { range: { 'dateRange.start': { gt: formattedDueThisWeek } } }, + ]; + break; + case 'High Priority': + taskFilter = [{ eq: { 'priority.label': 'High' } }]; + break; + case 'Unassigned': + taskFilter = [{ eq: { 'assignee.id': null } }]; + break; + default: + break; + } + } + return taskFilter; + } + get searchPlaceholder() { return `Search ${this.activeFilter.displayName}`; } @@ -482,6 +533,8 @@ class CrmAppTemplate extends Component { @context={{@context}} @realmURL={{this.currentRealm}} @viewCard={{this.viewCard}} + @searchFilter={{this.searchFilter}} + @taskFilter={{this.taskFilter}} /> {{else if this.query}} {{#if (eq this.selectedView 'card')}} diff --git a/packages/experiments-realm/crm/shared.gts b/packages/experiments-realm/crm/shared.gts new file mode 100644 index 0000000000..4c5bc25e09 --- /dev/null +++ b/packages/experiments-realm/crm/shared.gts @@ -0,0 +1,38 @@ +import AlertHexagon from '@cardstack/boxel-icons/alert-hexagon'; +import CalendarStar from '@cardstack/boxel-icons/calendar-star'; +import CalendarMonth from '@cardstack/boxel-icons/calendar-month'; +import ChevronsUp from '@cardstack/boxel-icons/chevrons-up'; +import UserQuestion from '@cardstack/boxel-icons/user-question'; + +export const taskStatusValues = [ + { + index: 0, + icon: AlertHexagon, + label: 'Overdue', + value: 'overdue', + }, + { + index: 1, + icon: CalendarStar, + label: 'Due Today', + value: 'due-today', + }, + { + index: 2, + icon: CalendarMonth, + label: 'Due this week', + value: 'due-this-week', + }, + { + index: 3, + icon: ChevronsUp, + label: 'High Priority', + value: 'high-priority', + }, + { + index: 4, + icon: UserQuestion, + label: 'Unassigned', + value: 'unassigned', + }, +]; diff --git a/packages/experiments-realm/crm/task-planner.gts b/packages/experiments-realm/crm/task-planner.gts index d3b79031fd..16992e1fa2 100644 --- a/packages/experiments-realm/crm/task-planner.gts +++ b/packages/experiments-realm/crm/task-planner.gts @@ -3,7 +3,7 @@ import { CRMTaskStatusField } from './task'; import GlimmerComponent from '@glimmer/component'; import { TaskPlanner, TaskCard } from '../components/base-task-planner'; import type { LooseSingleCardDocument } from '@cardstack/runtime-common'; -import type { Query } from '@cardstack/runtime-common/query'; +import type { Query, Filter } from '@cardstack/runtime-common/query'; import { getCards } from '@cardstack/runtime-common'; import { DndItem } from '@cardstack/boxel-ui/components'; import { AppCard } from '../app-card'; @@ -14,6 +14,8 @@ interface CRMTaskPlannerArgs { context: CardContext | undefined; realmURL: URL | undefined; viewCard: () => void; + searchFilter?: Filter[]; + taskFilter?: Filter[]; }; Element: HTMLElement; } @@ -40,6 +42,14 @@ export class CRMTaskPlanner extends GlimmerComponent { everyArr.push({ eq: { 'crmApp.id': this.parentId } }); } + if (this.args.searchFilter) { + everyArr.push(...this.args.searchFilter); + } + + if (this.args.taskFilter) { + everyArr.push(...this.args.taskFilter); + } + return everyArr.length > 0 ? { filter: {