From 38323f0ae4479e1451348fa83716a0de5a288b32 Mon Sep 17 00:00:00 2001 From: lucas Date: Sun, 9 Jun 2024 19:00:43 +0800 Subject: [PATCH 01/17] create: lead-form --- .../ca5a2eb1-fb9e-4467-a7aa-54f3ec316ee9.json | 42 ++ packages/drafts-realm/currency-amount.gts | 121 +++++ packages/drafts-realm/lead-form.gts | 431 ++++++++++++++++++ 3 files changed, 594 insertions(+) create mode 100644 packages/drafts-realm/LeadForm/ca5a2eb1-fb9e-4467-a7aa-54f3ec316ee9.json create mode 100644 packages/drafts-realm/currency-amount.gts create mode 100644 packages/drafts-realm/lead-form.gts diff --git a/packages/drafts-realm/LeadForm/ca5a2eb1-fb9e-4467-a7aa-54f3ec316ee9.json b/packages/drafts-realm/LeadForm/ca5a2eb1-fb9e-4467-a7aa-54f3ec316ee9.json new file mode 100644 index 0000000000..e2a4177ed9 --- /dev/null +++ b/packages/drafts-realm/LeadForm/ca5a2eb1-fb9e-4467-a7aa-54f3ec316ee9.json @@ -0,0 +1,42 @@ +{ + "data": { + "type": "card", + "attributes": { + "name": { + "salutation": "Mr.", + "firstName": "Loo", + "lastName": "Mok Sun" + }, + "company": "Loo Holding", + "title": "Title Here", + "website": "www.loobholding.com", + "description": "Description Here", + "leadStatus": "New", + "phone": "0109090000", + "email": "loobholding@xxxx.com", + "addressInfo": { + "address": "14, Jalan Teknologi, Taman Sains", + "zip": "47810", + "city": "Petaling Jaya", + "state": "Selangor", + "country": "Malaysia", + "countryCode": "MY", + "stateCode": "10" + }, + "noOfEmployees": 500.5, + "annualRevenue": { + "currency": "RM", + "totalAmount": 150300 + }, + "leadSource": "Advertisement", + "industry": "Banking", + "thumbnailURL": null + }, + "meta": { + "adoptsFrom": { + "module": "../lead-form", + "name": "LeadForm" + } + } + } +} \ No newline at end of file diff --git a/packages/drafts-realm/currency-amount.gts b/packages/drafts-realm/currency-amount.gts new file mode 100644 index 0000000000..b340e42f8d --- /dev/null +++ b/packages/drafts-realm/currency-amount.gts @@ -0,0 +1,121 @@ +import { + Component, + FieldDef, + contains, + field, +} from 'https://cardstack.com/base/card-api'; +import StringField from 'https://cardstack.com/base/string'; +import NumberField from 'https://cardstack.com/base/number'; +import { tracked } from '@glimmer/tracking'; +import { action } from '@ember/object'; +import { + BoxelInput, + BoxelSelect, + CardContainer, + FieldContainer, +} from '@cardstack/boxel-ui/components'; + +const formatNumber = (val: number) => { + let formatter = new Intl.NumberFormat('en-US', { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }); + return formatter.format(val); +}; + +class EmbeddedSecForAmount extends Component { + get getFormattedAmount() { + if (!this.args.model.totalAmount) return null; + const formattedNumber = formatNumber(this.args.model.totalAmount); + return formattedNumber; + } + + +} + +class EditSecForAmount extends Component { + @tracked currencyOptions = ['Select', 'RM', 'USD']; + get selectedCurrency() { + return this.args.model.currency || this.currencyOptions[0] || 'Select'; + } + + @action + updateCurrency(item: string) { + this.args.model.currency = item; + } + + @action + updateAmount(val: number) { + this.args.model.totalAmount = val; + } + + get getFormattedAmount() { + if (!this.args.model.totalAmount) return null; + const formattedNumber = formatNumber(this.args.model.totalAmount); + return formattedNumber; + } + + +} + +export class CurrencyAmount extends FieldDef { + static displayName = 'Currency Amount'; + @field currency = contains(StringField, { + description: `Currency`, + }); + + @field totalAmount = contains(NumberField, { + description: `Total Amount`, + }); + + static embedded = EmbeddedSecForAmount; + static edit = EditSecForAmount; +} diff --git a/packages/drafts-realm/lead-form.gts b/packages/drafts-realm/lead-form.gts new file mode 100644 index 0000000000..0037c9eb59 --- /dev/null +++ b/packages/drafts-realm/lead-form.gts @@ -0,0 +1,431 @@ +import MarkdownField from 'https://cardstack.com/base/markdown'; +import { + BaseDefComponent, + CardDef, + contains, + field, +} from 'https://cardstack.com/base/card-api'; +import StringField from 'https://cardstack.com/base/string'; +import NumberField from 'https://cardstack.com/base/number'; +import { UserName } from './user-name'; +import { UserEmail } from './user-email'; +import { AddressInfo } from './address-info'; +import { Component } from 'https://cardstack.com/base/card-api'; +import { CardContainer, FieldContainer } from '@cardstack/boxel-ui/components'; +import { BoxelSelect } from '@cardstack/boxel-ui/components'; +import { tracked } from '@glimmer/tracking'; +import { action } from '@ember/object'; +import { BoxelInput } from '@cardstack/boxel-ui/components'; +import { CurrencyAmount } from './currency-amount'; + +interface CategorySignature { + name: string; +} + +class IsolatedSecForLeadForm extends Component { + get getFormattedNoOfEmployees() { + if (!this.args.model.noOfEmployees) return null; + return Math.round(this.args.model.noOfEmployees); + } + + +} + +class ViewSecForLeadForm extends Component { + +} + +class EditSecFoLeadForm extends Component { + /* Lead Status Options */ + get selectedLeadStatus() { + return { name: this.args.model.leadStatus || 'None' }; + } + + @tracked leadStatusOptions = [ + { name: 'None' }, + { name: 'New' }, + { name: 'Working' }, + { name: 'Nurturing' }, + { name: 'Qualified' }, + { name: 'Unqualified' }, + ] as Array; + + @action updateLeadStatus(type: { name: string }) { + this.args.model.leadStatus = type.name; + } + + /* No Of Employees */ + @action updateNoOfEmployees(val: number) { + this.args.model.noOfEmployees = val; + } + + get getFormattedNoOfEmployees() { + if (!this.args.model.noOfEmployees) return null; + return Math.round(this.args.model.noOfEmployees); + } + + /* Lead Source Options */ + get selectedLeadSource() { + return { name: this.args.model.leadSource || 'None' }; + } + + @tracked leadSourceOptions = [ + { name: 'None' }, + { name: 'Advertisement' }, + { name: 'Employee Referral' }, + { name: 'External Referral' }, + { name: 'Partner' }, + { name: 'Public Relations' }, + { name: 'Seminar - Internal' }, + { name: 'Seminar - Partner' }, + { name: 'Trade Show' }, + { name: 'Web' }, + { name: 'Word of mouth' }, + { name: 'Other' }, + ] as Array; + + @action updateLeadSource(type: { name: string }) { + this.args.model.leadSource = type.name; + } + + /* Industry Options */ + get selectedIndustry() { + return { name: this.args.model.industry || 'None' }; + } + + @tracked industryOptions = [ + { name: 'None' }, + { name: 'Agriculture' }, + { name: 'Apparel' }, + { name: 'Banking' }, + { name: 'Biotechnology' }, + { name: 'Chemicals' }, + { name: 'Communications' }, + { name: 'Construction' }, + { name: 'Consulting' }, + { name: 'Education' }, + { name: 'Electronics' }, + { name: 'Energy' }, + { name: 'Engineering' }, + { name: 'Entertainment' }, + { name: 'Environmental' }, + { name: 'Finance' }, + { name: 'Food & Beverage' }, + { name: 'Government' }, + { name: 'Healthcare' }, + { name: 'Hospitality' }, + { name: 'Insurance' }, + { name: 'Machinery' }, + { name: 'Manufacturing' }, + { name: 'Media' }, + { name: 'Not For Profit' }, + { name: 'Recreation' }, + { name: 'Retail' }, + { name: 'Shipping' }, + { name: 'Technology' }, + { name: 'Telecommunications' }, + { name: 'Transportation' }, + { name: 'Utilities' }, + { name: 'Others' }, + ] as Array; + + @action updateIndustry(type: { name: string }) { + this.args.model.industry = type.name; + } + + +} + +export class LeadForm extends CardDef { + static displayName = 'Lead Form'; + + @field name = contains(UserName, { + description: `User's Full Name`, + }); + @field company = contains(StringField, { + description: `User's Company Name`, + }); + @field title = contains(StringField, { + description: `User's Title`, + }); + @field website = contains(StringField, { + description: `User's Website`, + }); + @field description = contains(MarkdownField, { + description: `User's Description`, + }); + @field leadStatus = contains(StringField, { + description: `Lead Status`, + }); + @field phone = contains(StringField, { + description: `User's phone number`, + }); + @field email = contains(UserEmail, { + description: `User's Email`, + }); + @field addressInfo = contains(AddressInfo, { + description: `User's AddressInfo`, + }); + @field noOfEmployees = contains(NumberField, { + description: `No Of Employees`, + }); + @field annualRevenue = contains(CurrencyAmount, { + description: `Annual Revenue`, + }); + @field leadSource = contains(StringField, { + description: `Lead Source`, + }); + @field industry = contains(StringField, { + description: `Industry`, + }); + + static isolated = IsolatedSecForLeadForm; + static atom = ViewSecForLeadForm; + static embedded = ViewSecForLeadForm; + static edit = EditSecFoLeadForm; +} From 883300143f7b858aea674107413453257e409a4f Mon Sep 17 00:00:00 2001 From: lucas Date: Mon, 10 Jun 2024 12:02:53 +0800 Subject: [PATCH 02/17] create: lead-form --- packages/drafts-realm/lead-form.gts | 87 +++++++++++++++++++++++++---- 1 file changed, 75 insertions(+), 12 deletions(-) diff --git a/packages/drafts-realm/lead-form.gts b/packages/drafts-realm/lead-form.gts index 0037c9eb59..517d40eb15 100644 --- a/packages/drafts-realm/lead-form.gts +++ b/packages/drafts-realm/lead-form.gts @@ -109,7 +109,6 @@ class IsolatedSecForLeadForm extends Component { display: grid; gap: var(--boxel-sp-lg); overflow: hidden; - box-shadow: 0px !important; } section { overflow: hidden; @@ -157,25 +156,89 @@ class IsolatedSecForLeadForm extends Component { class ViewSecForLeadForm extends Component { } From ee5d95377d48c2e27c5b275d238697310d646c2e Mon Sep 17 00:00:00 2001 From: lucas Date: Mon, 10 Jun 2024 15:14:43 +0800 Subject: [PATCH 03/17] adjust css: form-row-full --- packages/drafts-realm/currency-amount.gts | 19 ++++- packages/drafts-realm/lead-form.gts | 93 ++++++++++++----------- 2 files changed, 63 insertions(+), 49 deletions(-) diff --git a/packages/drafts-realm/currency-amount.gts b/packages/drafts-realm/currency-amount.gts index b340e42f8d..4e566a078e 100644 --- a/packages/drafts-realm/currency-amount.gts +++ b/packages/drafts-realm/currency-amount.gts @@ -11,7 +11,6 @@ import { action } from '@ember/object'; import { BoxelInput, BoxelSelect, - CardContainer, FieldContainer, } from '@cardstack/boxel-ui/components'; @@ -58,7 +57,7 @@ class EditSecForAmount extends Component { } } diff --git a/packages/drafts-realm/lead-form.gts b/packages/drafts-realm/lead-form.gts index 517d40eb15..ab75630b58 100644 --- a/packages/drafts-realm/lead-form.gts +++ b/packages/drafts-realm/lead-form.gts @@ -1,10 +1,5 @@ import MarkdownField from 'https://cardstack.com/base/markdown'; -import { - BaseDefComponent, - CardDef, - contains, - field, -} from 'https://cardstack.com/base/card-api'; +import { CardDef, contains, field } from 'https://cardstack.com/base/card-api'; import StringField from 'https://cardstack.com/base/string'; import NumberField from 'https://cardstack.com/base/number'; import { UserName } from './user-name'; @@ -160,54 +155,65 @@ class ViewSecForLeadForm extends Component {
About
- - <@fields.name @format='edit' /> - - - <@fields.company @format='edit' /> - - - <@fields.title @format='edit' /> - - - <@fields.website @format='edit' /> - - - <@fields.description @format='edit' /> - - - <@fields.leadStatus @format='edit' /> + + <@fields.name @format='edit' /> + + + <@fields.company @format='edit' /> + + + <@fields.title @format='edit' /> + + + <@fields.website @format='edit' /> + + + <@fields.description @format='edit' /> + + + <@fields.leadStatus @format='edit' /> +
Get In Touch
- - <@fields.phone @format='edit' /> - - - <@fields.email @format='edit' /> - - - <@fields.addressInfo @format='edit' /> + + <@fields.phone @format='edit' /> + + + <@fields.email @format='edit' /> + + + <@fields.addressInfo @format='edit' /> +
Segment
- - <@fields.noOfEmployees @format='edit' /> - - - <@fields.annualRevenue @format='edit' /> - - - <@fields.leadSource @format='edit' /> - - - <@fields.industry @format='edit' /> + + <@fields.noOfEmployees @format='edit' /> + + + <@fields.annualRevenue @format='edit' /> + + + <@fields.leadSource @format='edit' /> + + + <@fields.industry @format='edit' /> +
@@ -236,9 +242,6 @@ class ViewSecForLeadForm extends Component { border-radius: var(--boxel-border-radius); padding: var(--boxel-sp); } - label { - font-weight: 700; - } } From 27e4cf5781835cdce749c1e13b76ab90cb74a266 Mon Sep 17 00:00:00 2001 From: lucas Date: Wed, 26 Jun 2024 22:51:38 +0800 Subject: [PATCH 04/17] create: steps staging & activity status panel --- .../fe2e7e4c-796c-4a59-93a0-c2463a10ffdc.json | 42 + .../bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json | 77 + packages/drafts-realm/contact-form.gts | 43 +- packages/drafts-realm/lead-form.gts | 56 +- packages/drafts-realm/opportunity-form.gts | 37 +- packages/drafts-realm/sale-hub.gts | 1313 +++++++++++++++++ 6 files changed, 1560 insertions(+), 8 deletions(-) create mode 100644 packages/drafts-realm/LeadForm/fe2e7e4c-796c-4a59-93a0-c2463a10ffdc.json create mode 100644 packages/drafts-realm/SaleHub/bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json create mode 100644 packages/drafts-realm/sale-hub.gts diff --git a/packages/drafts-realm/LeadForm/fe2e7e4c-796c-4a59-93a0-c2463a10ffdc.json b/packages/drafts-realm/LeadForm/fe2e7e4c-796c-4a59-93a0-c2463a10ffdc.json new file mode 100644 index 0000000000..28c0265218 --- /dev/null +++ b/packages/drafts-realm/LeadForm/fe2e7e4c-796c-4a59-93a0-c2463a10ffdc.json @@ -0,0 +1,42 @@ +{ + "data": { + "type": "card", + "attributes": { + "name": { + "salutation": "Mr.", + "firstName": "Chow", + "lastName": "Chow " + }, + "company": "Chow Holding", + "title": "Lead Form - Chow Chow", + "website": "www.chow.com", + "description": "Description Here", + "leadStatus": "Qualified", + "phone": "0109090000", + "email": "chow@xxxx.com", + "addressInfo": { + "address": "14, Jalan Teknologi, Taman Sains", + "zip": "47810", + "city": "Petaling Jaya", + "state": "Selangor", + "country": "Malaysia", + "countryCode": "MY", + "stateCode": "10" + }, + "noOfEmployees": 49.8, + "annualRevenue": { + "currency": "RM", + "totalAmount": 12000000 + }, + "leadSource": "Employee Referral", + "industry": "Banking", + "thumbnailURL": null + }, + "meta": { + "adoptsFrom": { + "module": "../lead-form", + "name": "LeadForm" + } + } + } +} \ No newline at end of file diff --git a/packages/drafts-realm/SaleHub/bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json b/packages/drafts-realm/SaleHub/bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json new file mode 100644 index 0000000000..a7f2c56fd8 --- /dev/null +++ b/packages/drafts-realm/SaleHub/bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json @@ -0,0 +1,77 @@ +{ + "data": { + "type": "card", + "attributes": { + "leadForm": { + "name": { + "salutation": "Mr.", + "firstName": "Law", + "lastName": "Bom Bom" + }, + "company": "lawbombom", + "title": "Lead Form - lawbombom", + "website": "lawbom.com", + "description": "this is lead-form", + "leadStatus": "Unqualified", + "phone": "010381997", + "email": "lawbombom@gmail.com", + "addressInfo": { + "address": "14, Jalan Teknologi, Taman Sains", + "zip": "47810", + "city": "Petaling Jaya", + "state": "Selangor", + "country": "Malaysia", + "countryCode": "MY", + "stateCode": "10" + }, + "noOfEmployees": 10, + "annualRevenue": { + "currency": "RM", + "totalAmount": 125000 + }, + "leadSource": "Advertisement", + "industry": "Agriculture" + }, + "contactForm": { + "name": { + "salutation": "Mr.", + "firstName": "Law", + "lastName": "Bom Bom" + }, + "company": null, + "title": null, + "website": null, + "description": null, + "leadStatus": null, + "phone": null, + "email": null, + "addressInfo": { + "address": null, + "zip": null, + "city": null, + "state": null, + "country": null, + "countryCode": null, + "stateCode": null + }, + "noOfEmployees": null, + "annualRevenue": { + "currency": null, + "totalAmount": null + }, + "leadSource": null, + "industry": null + }, + "scheduledTask": [], + "title": null, + "description": null, + "thumbnailURL": null + }, + "meta": { + "adoptsFrom": { + "module": "../sale-hub", + "name": "SaleHub" + } + } + } +} \ No newline at end of file diff --git a/packages/drafts-realm/contact-form.gts b/packages/drafts-realm/contact-form.gts index e7352b63f1..92f14c0ae7 100644 --- a/packages/drafts-realm/contact-form.gts +++ b/packages/drafts-realm/contact-form.gts @@ -1,7 +1,12 @@ import { UserName } from './user-name'; import { UserEmail } from './user-email'; import { AddressInfo } from './address-info'; -import { CardDef, field, contains } from 'https://cardstack.com/base/card-api'; +import { + CardDef, + field, + contains, + FieldDef, +} from 'https://cardstack.com/base/card-api'; import { Component } from 'https://cardstack.com/base/card-api'; import StringField from 'https://cardstack.com/base/string'; import { FieldContainer, CardContainer } from '@cardstack/boxel-ui/components'; @@ -134,19 +139,17 @@ class Isolated extends Component { class View extends Component { +// } + +// class ViewSecForLeadForm extends Component { +// +// } + +// class EditSecFoLeadForm extends Component { +// /* Lead Status Options */ +// get selectedLeadStatus() { +// return { name: this.args.model.leadStatus || 'None' }; +// } + +// @tracked leadStatusOptions = [ +// { name: 'None' }, +// { name: 'New' }, +// { name: 'Working' }, +// { name: 'Nurturing' }, +// { name: 'Qualified' }, +// { name: 'Unqualified' }, +// ] as Array; + +// @action updateLeadStatus(type: { name: string }) { +// this.args.model.leadStatus = type.name; +// } + +// /* No Of Employees */ +// @action updateNoOfEmployees(val: number) { +// this.args.model.noOfEmployees = val; +// } + +// get getFormattedNoOfEmployees() { +// if (!this.args.model.noOfEmployees) return null; +// return Math.round(this.args.model.noOfEmployees); +// } + +// /* Lead Source Options */ +// get selectedLeadSource() { +// return { name: this.args.model.leadSource || 'None' }; +// } + +// @tracked leadSourceOptions = [ +// { name: 'None' }, +// { name: 'Advertisement' }, +// { name: 'Employee Referral' }, +// { name: 'External Referral' }, +// { name: 'Partner' }, +// { name: 'Public Relations' }, +// { name: 'Seminar - Internal' }, +// { name: 'Seminar - Partner' }, +// { name: 'Trade Show' }, +// { name: 'Web' }, +// { name: 'Word of mouth' }, +// { name: 'Other' }, +// ] as Array; + +// @action updateLeadSource(type: { name: string }) { +// this.args.model.leadSource = type.name; +// } + +// /* Industry Options */ +// get selectedIndustry() { +// return { name: this.args.model.industry || 'None' }; +// } + +// @tracked industryOptions = [ +// { name: 'None' }, +// { name: 'Agriculture' }, +// { name: 'Apparel' }, +// { name: 'Banking' }, +// { name: 'Biotechnology' }, +// { name: 'Chemicals' }, +// { name: 'Communications' }, +// { name: 'Construction' }, +// { name: 'Consulting' }, +// { name: 'Education' }, +// { name: 'Electronics' }, +// { name: 'Energy' }, +// { name: 'Engineering' }, +// { name: 'Entertainment' }, +// { name: 'Environmental' }, +// { name: 'Finance' }, +// { name: 'Food & Beverage' }, +// { name: 'Government' }, +// { name: 'Healthcare' }, +// { name: 'Hospitality' }, +// { name: 'Insurance' }, +// { name: 'Machinery' }, +// { name: 'Manufacturing' }, +// { name: 'Media' }, +// { name: 'Not For Profit' }, +// { name: 'Recreation' }, +// { name: 'Retail' }, +// { name: 'Shipping' }, +// { name: 'Technology' }, +// { name: 'Telecommunications' }, +// { name: 'Transportation' }, +// { name: 'Utilities' }, +// { name: 'Others' }, +// ] as Array; + +// @action updateIndustry(type: { name: string }) { +// this.args.model.industry = type.name; +// } + +// +// } + +// class LeadForm extends FieldDef { +// static displayName = 'Lead Form'; + +// @field name = contains(UserName, { +// description: `User's Full Name`, +// }); +// @field company = contains(StringField, { +// description: `User's Company Name`, +// }); +// @field title = contains(StringField, { +// description: `User's Title`, +// }); +// @field website = contains(StringField, { +// description: `User's Website`, +// }); +// @field description = contains(MarkdownField, { +// description: `User's Description`, +// }); +// @field leadStatus = contains(StringField, { +// description: `Lead Status`, +// }); +// @field phone = contains(StringField, { +// description: `User's phone number`, +// }); +// @field email = contains(UserEmail, { +// description: `User's Email`, +// }); +// @field addressInfo = contains(AddressInfo, { +// description: `User's AddressInfo`, +// }); +// @field noOfEmployees = contains(NumberField, { +// description: `No Of Employees`, +// }); +// @field annualRevenue = contains(CurrencyAmount, { +// description: `Annual Revenue`, +// }); +// @field leadSource = contains(StringField, { +// description: `Lead Source`, +// }); +// @field industry = contains(StringField, { +// description: `Industry`, +// }); + +// static isolated = IsolatedSecForLeadForm; +// static atom = ViewSecForLeadForm; +// static embedded = ViewSecForLeadForm; +// static edit = EditSecFoLeadForm; +// } + +//*task-form +class TaskForm extends FieldDef { + static displayName = 'Task Form'; + + @field taskId = contains(StringField, { + description: `Task Id`, + }); + @field subject = contains(StringField, { + description: `Subject`, + }); + @field dueDate = contains(DateCard, { + description: `Due Date`, + }); + @field comments = contains(MarkdownField, { + description: `Comments`, + }); + @field relatedTo = linksTo(CrmAccount, { + description: `Related to Crm Account`, + }); + @field isCompleted = contains(BooleanField, { + description: `Is Task Completed`, + }); +} + +//*steps +class Steps extends GlimmerComponent { + @tracked steps = this.args.steps.map((step) => ({ ...step })); + @tracked currentStepName = this.steps[0].name || ''; + @tracked currentStepIndex = this.steps[0].step || 0; + + get isStepCompleted() { + return this.currentStepStatus === 'Step Completed'; + } + + get currentStepStatus() { + if (this.currentStepIndex >= this.steps.length - 1) return 'Convert'; + if (this.steps[this.currentStepIndex].isCompleted) return 'Step Completed'; + return 'Mark Status as Complete'; + } + + @action + handleOnStepClick(clickedStep: number) { + this.currentStepIndex = clickedStep; + this.currentStepName = this.steps[this.currentStepIndex].name; + + this.steps = this.steps.map((step, index) => { + return { ...step, isActive: index === clickedStep }; + }); + } + + @action handleCompleteStep() { + if (this.currentStepIndex < this.steps.length - 1) { + this.steps = this.steps.map((step, index) => { + return { + ...step, + isCompleted: index <= this.currentStepIndex, + isProceedToNextStep: index === this.currentStepIndex + 1, + }; + }); + } + + this.args.updateLeadStatus(this.currentStepName); + } + + @action + handleButtonClick() { + if (this.currentStepStatus === 'Convert') { + this.args.handleConvert(); + } else { + this.handleCompleteStep(); + } + } + + +} + +//*contact-form-field + +class ContactFormField extends LeadFormField { + static displayName = 'Contact Form'; + @field name = contains(UserName, { + description: `User's Full Name`, + }); +} + +//*convert lead form +// class EditSecFoLeadConvertedFormField extends Component< +// typeof LeadConvertedFormField +// > { +// /* Lead Status Options */ +// get selectedConvertedStatus() { +// return { name: this.args.model.convertedStatus || 'None' }; +// } + +// @tracked convertedStatusOptions = [ +// { name: 'None' }, +// { name: 'New' }, +// { name: 'Working' }, +// { name: 'Nurturing' }, +// { name: 'Qualified' }, +// { name: 'Unqualified' }, +// ] as Array; + +// @action updateConvertedStatus(type: { name: string }) { +// this.args.model.convertedStatus = type.name; +// } + +// +// } + +// class LeadConvertedFormField extends FieldDef { +// static displayName = 'Convert Lead Form'; +// @field contactForm = linksTo(ContactForm); +// @field opportunityForm = contains(OpportunityFormField); +// @field convertedStatus = contains(StringField); + +// static edit = EditSecFoLeadConvertedFormField; +// } + +//*scheduled task +class ScheduledTask extends FieldDef { + static displayName = 'Scheduled Task'; + @field taskForm = contains(TaskForm); + + static embedded = class Embedded extends Component { + + }; +} + +class IsolatedSecForSaleHub extends Component { + //convert lead-form Modal + @tracked isModalVisible = false; + + @action + toggleModal() { + this.isModalVisible = !this.isModalVisible; + } + + @action + openModal() { + this.isModalVisible = true; + } + + @action + closeModal() { + this.isModalVisible = false; + } + + @action + handleConvert() { + this.openModal(); + } + + //task-form modal + @tracked isTaskFormModalVisible = false; + + @action + openTaskFormModal() { + this.isTaskFormModalVisible = true; + } + + @action + closeTaskFormModal() { + this.isTaskFormModalVisible = false; + } + + //auto bind contact-form + + get contactFormName() { + if ( + this.args.model && + this.args.model.contactForm && + this.args.model.leadForm + ) { + const salutation = this.args.model.leadForm.name.salutation; + const firstName = this.args.model.leadForm.name.firstName; + const lastName = this.args.model.leadForm.name.lastName; + + this.args.model.contactForm.name.salutation = salutation; + this.args.model.contactForm.name.firstName = firstName; + this.args.model.contactForm.name.lastName = lastName; + + return salutation + ' ' + firstName + ' ' + lastName; + } + return ''; // Ensure a string is always returned + } + + //step + @tracked initStepOptions = [ + { + step: 0, + name: 'New', + isActive: true, + isCompleted: false, + isProceedToNextStep: false, + }, + { + step: 1, + name: 'Working', + isActive: false, + isCompleted: false, + isProceedToNextStep: false, + }, + { + step: 2, + name: 'Nurturing', + isActive: false, + isCompleted: false, + isProceedToNextStep: false, + }, + { + step: 3, + name: 'Unqualified', + isActive: false, + isCompleted: false, + isProceedToNextStep: false, + }, + { + step: 4, + name: 'Converted', + isActive: false, + isCompleted: false, + isProceedToNextStep: false, + }, + ] as Array; + + get stepOptions() { + let currentCompletedStep = this.initStepOptions.findIndex( + (option) => option.name === this.args.model.leadForm?.leadStatus, + ); + + this.initStepOptions.forEach((option, index) => { + option.isCompleted = index <= currentCompletedStep; + option.isProceedToNextStep = index === currentCompletedStep + 1; + }); + + return this.initStepOptions; + } + + @action updateLeadStatus(status: string) { + if (!(this.args.model.leadForm && this.args.model.leadForm.leadStatus)) + return; + + this.args.model.leadForm.leadStatus = status; + } + + //upcoming task list + get tasks() { + if (!this.args.model.scheduledTask) return []; + + const mapTasks = this.args.model.scheduledTask.map((task) => { + return { + month: task.taskForm.dueDate + ? this.formatDueDate(task.taskForm.dueDate) + : null, + taskId: task.taskForm.taskId, + subject: task.taskForm.subject, + dueDate: task.taskForm.dueDate, + comments: task.taskForm.comments, + isCompleted: task.taskForm.isCompleted, + }; + }); + return mapTasks; + } + + get groupedTasks() { + if (!this.tasks) return; + + const groupedTasks: { [key: string]: GroupedTasksSignature[] } = + this.tasks.reduce((acc: any, task: GroupedTasksSignature) => { + if (!acc[task.month]) { + acc[task.month] = []; + } + acc[task.month].push(task); + return acc; + }, {}); + + const sortedMonths = Object.keys(groupedTasks).sort((a, b) => { + if (a === 'Upcoming & Overdue') return -1; + if (b === 'Upcoming & Overdue') return 1; + return new Date(a).getTime() - new Date(b).getTime(); + }); + + const sortedGroupedTasks: any = sortedMonths.reduce( + (acc: any, month: string) => { + acc[month] = groupedTasks[month].sort( + (a, b) => + new Date(a.dueDate).getTime() - new Date(b.dueDate).getTime(), + ); + return acc; + }, + {}, + ); + + return sortedGroupedTasks; + } + + @action formatDueDate(date: Date) { + const todayDate = new Date(); + if (date > todayDate || isToday(date)) + return formatDate(new Date(date), 'MMMM yyyy'); + return 'Upcoming & Overdue'; + } + + @action formatDay(date: Date) { + const todayDate = new Date(); + if (isToday(date)) return 'Today'; + if (isTomorrow(date)) return 'Tomorrow'; + if (date < todayDate) return null; + if (isThisMonth(date)) return 'This Month'; + return null; + } + + @action toggleOnCheckTask(taskIdChecked: string) { + this.args.model.scheduledTask = this.args.model.scheduledTask?.map( + (task) => { + task.taskForm.taskId === taskIdChecked + ? (task.taskForm.isCompleted = !task.taskForm.isCompleted) + : task.taskForm.isCompleted; + + return task; + }, + ); + + return this.args.model.scheduledTask; + } + + +} + +export class SaleHub extends CardDef { + static displayName = 'sale hub'; + @field leadForm = contains(LeadFormField); + // @field leadConvertedForm = contains(LeadConvertedFormField); + // @field accountForm = linksTo(CrmAccount); + @field contactForm = contains(ContactFormField); + // @field opportunityForm = linksTo(OpportunityForm); + @field scheduledTask = containsMany(ScheduledTask); + static isolated = IsolatedSecForSaleHub; +} From 4623e26a608e3a27ccaef01e8ad755df8cde352b Mon Sep 17 00:00:00 2001 From: lucas Date: Thu, 27 Jun 2024 18:47:43 +0800 Subject: [PATCH 05/17] remove leadConvertedForm --- .../bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json | 85 +++++++- packages/drafts-realm/crm/account.gts | 32 +++ packages/drafts-realm/sale-hub.gts | 193 ++++++++++-------- 3 files changed, 211 insertions(+), 99 deletions(-) diff --git a/packages/drafts-realm/SaleHub/bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json b/packages/drafts-realm/SaleHub/bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json index a7f2c56fd8..6dacede677 100644 --- a/packages/drafts-realm/SaleHub/bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json +++ b/packages/drafts-realm/SaleHub/bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json @@ -32,19 +32,55 @@ "leadSource": "Advertisement", "industry": "Agriculture" }, + "accountForm": { + "accountName": "Mr. Law Bom Bom", + "accountAlias": null, + "description": null, + "contactInformation": { + "salutation": "Mr.", + "firstName": "Law", + "lastName": "Bom Bom", + "address": { + "streetAddress": null, + "city": null, + "region": null, + "postalCode": null, + "poBoxNumber": null, + "country": null + }, + "phoneNumber": { + "country": null, + "area": null, + "number": null + }, + "email": { + "value": null + }, + "website": { + "value": null + } + }, + "billingAddress": { + "streetAddress": null, + "city": null, + "region": null, + "postalCode": null, + "poBoxNumber": null, + "country": null + }, + "numberOfEmployees": null + }, "contactForm": { + "title": null, "name": { "salutation": "Mr.", "firstName": "Law", "lastName": "Bom Bom" }, - "company": null, - "title": null, - "website": null, - "description": null, - "leadStatus": null, - "phone": null, "email": null, + "phone": null, + "fax": null, + "department": null, "addressInfo": { "address": null, "zip": null, @@ -53,20 +89,47 @@ "country": null, "countryCode": null, "stateCode": null - }, - "noOfEmployees": null, - "annualRevenue": { + } + }, + "opportunityForm": { + "opportunityName": null, + "closeDate": null, + "amount": { "currency": null, "totalAmount": null }, - "leadSource": null, - "industry": null + "description": null, + "stage": null, + "percentage": null, + "forecastCategory": null }, "scheduledTask": [], "title": null, "description": null, "thumbnailURL": null }, + "relationships": { + "accountForm.owner": { + "links": { + "self": null + } + }, + "accountForm.parentAccount": { + "links": { + "self": null + } + }, + "accountForm.company": { + "links": { + "self": null + } + }, + "opportunityForm.accountName": { + "links": { + "self": null + } + } + }, "meta": { "adoptsFrom": { "module": "../sale-hub", diff --git a/packages/drafts-realm/crm/account.gts b/packages/drafts-realm/crm/account.gts index e3d3ca93c2..f669b8d586 100644 --- a/packages/drafts-realm/crm/account.gts +++ b/packages/drafts-realm/crm/account.gts @@ -4,6 +4,7 @@ import { CardDef, linksTo, Component, + FieldDef, } from 'https://cardstack.com/base/card-api'; import NumberField from 'https://cardstack.com/base/number'; @@ -33,6 +34,37 @@ export class Company extends CardDef { }; } +export class CrmAccountField extends FieldDef { + static displayName = 'Crm Account'; + @field owner = linksTo(MatrixUser); + @field accountName = contains(StringField); + @field accountAlias = contains(StringField); + @field description = contains(StringField); + @field contactInformation = contains(Contact); + @field billingAddress = contains(Address); + @field shippingAddress = contains(Address, { + computeVia: function (this: CrmAccount) { + return this.billingAddress; + }, + }); + @field numberOfEmployees = contains(NumberField); + @field parentAccount = linksTo(() => CrmAccount); + @field company = linksTo(Company); + @field title = contains(StringField, { + computeVia: function (this: CrmAccount) { + return this.accountName; + }, + }); + + static embedded = class Embedded extends Component { + + }; +} + export class CrmAccount extends CardDef { static displayName = 'Crm Account'; @field owner = linksTo(MatrixUser); diff --git a/packages/drafts-realm/sale-hub.gts b/packages/drafts-realm/sale-hub.gts index 06b409a945..c0e1fd2460 100644 --- a/packages/drafts-realm/sale-hub.gts +++ b/packages/drafts-realm/sale-hub.gts @@ -24,10 +24,8 @@ import { Modal, } from '@cardstack/boxel-ui/components'; import MarkdownField from '../base/markdown'; -import { CrmAccount } from './crm/account'; -import { AddressInfo } from './address-info'; +import { CrmAccount, CrmAccountField } from './crm/account'; import StringField from 'https://cardstack.com/base/string'; -import NumberField from 'https://cardstack.com/base/number'; import BooleanField from 'https://cardstack.com/base/boolean'; import { format as formatDate, @@ -35,11 +33,9 @@ import { isTomorrow, isThisMonth, } from 'date-fns'; -import { UserName } from './user-name'; -import { UserEmail } from './user-email'; -import { CurrencyAmount } from './currency-amount'; import { OpportunityFormField } from './opportunity-form'; -import { LeadFormField, LeadForm } from './lead-form'; +import { LeadFormField } from './lead-form'; +import { ContactFormField } from './contact-form'; interface StepSignature { step: number; @@ -758,77 +754,6 @@ class Steps extends GlimmerComponent { } -//*contact-form-field - -class ContactFormField extends LeadFormField { - static displayName = 'Contact Form'; - @field name = contains(UserName, { - description: `User's Full Name`, - }); -} - -//*convert lead form -// class EditSecFoLeadConvertedFormField extends Component< -// typeof LeadConvertedFormField -// > { -// /* Lead Status Options */ -// get selectedConvertedStatus() { -// return { name: this.args.model.convertedStatus || 'None' }; -// } - -// @tracked convertedStatusOptions = [ -// { name: 'None' }, -// { name: 'New' }, -// { name: 'Working' }, -// { name: 'Nurturing' }, -// { name: 'Qualified' }, -// { name: 'Unqualified' }, -// ] as Array; - -// @action updateConvertedStatus(type: { name: string }) { -// this.args.model.convertedStatus = type.name; -// } - -// -// } - -// class LeadConvertedFormField extends FieldDef { -// static displayName = 'Convert Lead Form'; -// @field contactForm = linksTo(ContactForm); -// @field opportunityForm = contains(OpportunityFormField); -// @field convertedStatus = contains(StringField); - -// static edit = EditSecFoLeadConvertedFormField; -// } - //*scheduled task class ScheduledTask extends FieldDef { static displayName = 'Scheduled Task'; @@ -871,6 +796,10 @@ class IsolatedSecForSaleHub extends Component { @action openTaskFormModal() { this.isTaskFormModalVisible = true; + + this.updateAccountFormAccountName(); + this.updateContactFormName(); + this.updateOpportunityAccountName(); } @action @@ -878,9 +807,22 @@ class IsolatedSecForSaleHub extends Component { this.isTaskFormModalVisible = false; } - //auto bind contact-form + //auto bind form + @action + updateAccountFormAccountName() { + if ( + this.args.model && + this.args.model.accountForm && + this.args.model.leadForm + ) { + const { salutation, firstName, lastName } = this.args.model.leadForm.name; + + this.args.model.accountForm.accountName = `${salutation} ${firstName} ${lastName}`; + } + } - get contactFormName() { + @action + updateContactFormName() { if ( this.args.model && this.args.model.contactForm && @@ -893,10 +835,57 @@ class IsolatedSecForSaleHub extends Component { this.args.model.contactForm.name.salutation = salutation; this.args.model.contactForm.name.firstName = firstName; this.args.model.contactForm.name.lastName = lastName; + } + } - return salutation + ' ' + firstName + ' ' + lastName; + @action + updateOpportunityAccountName() { + if ( + this.args.model && + this.args.model.opportunityForm && + this.args.model.leadForm + ) { + const firstName = this.args.model.leadForm.name.firstName; + const company = this.args.model.leadForm.company; + + this.args.model.opportunityForm.opportunityName = `${firstName} ${company}`; } - return ''; // Ensure a string is always returned + } + + get accountFormAccountName() { + const { leadForm } = this.args.model; + + if (!leadForm || !leadForm.name) return ''; + + const { salutation, firstName, lastName } = leadForm.name; + + if (!salutation || !firstName || !lastName) return ''; + + return `${salutation} ${firstName} ${lastName}`; + } + + get contactFormAccountName() { + const { leadForm } = this.args.model; + + if (!leadForm || !leadForm.name) return ''; + + const { salutation, firstName, lastName } = leadForm.name; + + if (!salutation || !firstName || !lastName) return ''; + + return `${salutation} ${firstName} ${lastName}`; + } + + get opportunityFormName() { + const { leadForm } = this.args.model; + + if (!leadForm || !leadForm.name || !leadForm.company) return ''; + + const { firstName } = leadForm.name; + + if (!firstName) return ''; + + return `${firstName} ${leadForm.company}`; } //step @@ -1058,9 +1047,33 @@ class IsolatedSecForSaleHub extends Component {

Convert Lead Form

- - {{this.contactFormName}} + + {{this.accountFormAccountName}} + + + + {{this.contactFormAccountName}} + + + {{this.opportunityFormName}} + +
@@ -1177,6 +1190,11 @@ class IsolatedSecForSaleHub extends Component { .container { padding: var(--boxel-sp); } + .input-container { + padding: var(--boxel-sp); + background-color: var(--boxel-100); + border-radius: var(--boxel-border-radius); + } .sale-hub-container { padding: var(--boxel-sp-xs); overflow: hidden; @@ -1295,7 +1313,7 @@ class IsolatedSecForSaleHub extends Component { color: var(--boxel-red); } .formInputGroup > * + * { - margin-top: 1rem; + margin-top: 2rem; } @@ -1304,10 +1322,9 @@ class IsolatedSecForSaleHub extends Component { export class SaleHub extends CardDef { static displayName = 'sale hub'; @field leadForm = contains(LeadFormField); - // @field leadConvertedForm = contains(LeadConvertedFormField); - // @field accountForm = linksTo(CrmAccount); + @field accountForm = contains(CrmAccountField); @field contactForm = contains(ContactFormField); - // @field opportunityForm = linksTo(OpportunityForm); + @field opportunityForm = contains(OpportunityFormField); @field scheduledTask = containsMany(ScheduledTask); static isolated = IsolatedSecForSaleHub; } From 312a9e99f973e4d5ca5d6266fcf8fe37f838bb54 Mon Sep 17 00:00:00 2001 From: lucas Date: Fri, 28 Jun 2024 10:35:44 +0800 Subject: [PATCH 06/17] add company name field at opportunity form, currently dont have this field --- .../37f6334a-02be-48db-a675-20409f8d5507.json | 1 + .../bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json | 1 + packages/drafts-realm/opportunity-form.gts | 20 +++++++++++++++++++ packages/drafts-realm/sale-hub.gts | 20 +++++-------------- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/packages/drafts-realm/OpportunityForm/37f6334a-02be-48db-a675-20409f8d5507.json b/packages/drafts-realm/OpportunityForm/37f6334a-02be-48db-a675-20409f8d5507.json index c8d7e1f929..a630c7f8ec 100644 --- a/packages/drafts-realm/OpportunityForm/37f6334a-02be-48db-a675-20409f8d5507.json +++ b/packages/drafts-realm/OpportunityForm/37f6334a-02be-48db-a675-20409f8d5507.json @@ -3,6 +3,7 @@ "type": "card", "attributes": { "opportunityName": "Opportunity Name", + "companyName": "", "closeDate": "2024-06-13", "amount": { "currency": "RM", diff --git a/packages/drafts-realm/SaleHub/bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json b/packages/drafts-realm/SaleHub/bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json index 6dacede677..124107945a 100644 --- a/packages/drafts-realm/SaleHub/bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json +++ b/packages/drafts-realm/SaleHub/bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json @@ -93,6 +93,7 @@ }, "opportunityForm": { "opportunityName": null, + "companyName": "Law lawbombom", "closeDate": null, "amount": { "currency": null, diff --git a/packages/drafts-realm/opportunity-form.gts b/packages/drafts-realm/opportunity-form.gts index 971266bb18..a4abc9addb 100644 --- a/packages/drafts-realm/opportunity-form.gts +++ b/packages/drafts-realm/opportunity-form.gts @@ -119,6 +119,11 @@ class AmountField extends FieldDef { /* Opportunity Form */ class IsolatedSecForOpportunityForm extends Component { + get getCompanyName() { + if (!this.args.model.companyName) return '-'; + return this.args.model.companyName; + } + get getFormattedAmount() { const amount = this.args.model.amount; const hasAmount = amount && amount.totalAmount; @@ -176,6 +181,11 @@ class IsolatedSecForOpportunityForm extends Component {
+
+ + + {{this.getCompanyName}} +
<@fields.closeDate /> @@ -338,6 +348,10 @@ class EditSecForOpportunityForm extends Component { <@fields.accountName /> + + <@fields.companyName /> + + <@fields.closeDate /> @@ -432,6 +446,9 @@ export class OpportunityFormField extends FieldDef { @field accountName = linksTo(CrmAccount, { description: `Account Name`, }); + @field companyName = contains(StringField, { + description: `Company Name`, + }); @field closeDate = contains(DateCard, { description: `Close Date`, }); @@ -465,6 +482,9 @@ export class OpportunityForm extends CardDef { @field accountName = linksTo(CrmAccount, { description: `Account Name`, }); + @field companyName = contains(StringField, { + description: `Company Name`, + }); @field closeDate = contains(DateCard, { description: `Close Date`, }); diff --git a/packages/drafts-realm/sale-hub.gts b/packages/drafts-realm/sale-hub.gts index c0e1fd2460..5316a7925d 100644 --- a/packages/drafts-realm/sale-hub.gts +++ b/packages/drafts-realm/sale-hub.gts @@ -788,6 +788,9 @@ class IsolatedSecForSaleHub extends Component { @action handleConvert() { this.openModal(); + this.updateAccountFormAccountName(); + this.updateContactFormName(); + this.updateOpportunityAccountName(); } //task-form modal @@ -796,10 +799,6 @@ class IsolatedSecForSaleHub extends Component { @action openTaskFormModal() { this.isTaskFormModalVisible = true; - - this.updateAccountFormAccountName(); - this.updateContactFormName(); - this.updateOpportunityAccountName(); } @action @@ -848,43 +847,34 @@ class IsolatedSecForSaleHub extends Component { const firstName = this.args.model.leadForm.name.firstName; const company = this.args.model.leadForm.company; - this.args.model.opportunityForm.opportunityName = `${firstName} ${company}`; + this.args.model.opportunityForm.companyName = `${firstName} ${company}`; } } get accountFormAccountName() { const { leadForm } = this.args.model; - if (!leadForm || !leadForm.name) return ''; - const { salutation, firstName, lastName } = leadForm.name; if (!salutation || !firstName || !lastName) return ''; - return `${salutation} ${firstName} ${lastName}`; } get contactFormAccountName() { const { leadForm } = this.args.model; - if (!leadForm || !leadForm.name) return ''; - const { salutation, firstName, lastName } = leadForm.name; if (!salutation || !firstName || !lastName) return ''; - return `${salutation} ${firstName} ${lastName}`; } get opportunityFormName() { const { leadForm } = this.args.model; - - if (!leadForm || !leadForm.name || !leadForm.company) return ''; - + if (!leadForm || !leadForm.company) return ''; const { firstName } = leadForm.name; if (!firstName) return ''; - return `${firstName} ${leadForm.company}`; } From 7e6313eeccc3c831154e12bc6bbd54d7c3303c48 Mon Sep 17 00:00:00 2001 From: lucas Date: Sat, 29 Jun 2024 10:55:29 +0800 Subject: [PATCH 07/17] seperate and resorting the date by upcoming, today date and overdue --- .../bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json | 59 ++++++++++++++++++- packages/drafts-realm/sale-hub.gts | 13 ++-- 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/packages/drafts-realm/SaleHub/bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json b/packages/drafts-realm/SaleHub/bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json index 124107945a..84cf186f46 100644 --- a/packages/drafts-realm/SaleHub/bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json +++ b/packages/drafts-realm/SaleHub/bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json @@ -104,7 +104,44 @@ "percentage": null, "forecastCategory": null }, - "scheduledTask": [], + "scheduledTask": [ + { + "taskForm": { + "taskId": "0", + "subject": "1", + "dueDate": "2024-06-29", + "comments": null, + "isCompleted": false + } + }, + { + "taskForm": { + "taskId": "1", + "subject": "task b", + "dueDate": "2024-06-27", + "comments": null, + "isCompleted": false + } + }, + { + "taskForm": { + "taskId": "2", + "subject": "task ccc", + "dueDate": "2024-06-30", + "comments": null, + "isCompleted": false + } + }, + { + "taskForm": { + "taskId": "3", + "subject": "task xx", + "dueDate": "2024-07-03", + "comments": null, + "isCompleted": false + } + } + ], "title": null, "description": null, "thumbnailURL": null @@ -129,6 +166,26 @@ "links": { "self": null } + }, + "scheduledTask.0.taskForm.relatedTo": { + "links": { + "self": "http://localhost:4201/drafts/CardDef/7298c9c6-2964-41a0-87f7-42127a16e12b" + } + }, + "scheduledTask.1.taskForm.relatedTo": { + "links": { + "self": "http://localhost:4201/drafts/CrmAccount/4802eeed-bec6-4d7a-8f05-6370866edd40" + } + }, + "scheduledTask.2.taskForm.relatedTo": { + "links": { + "self": null + } + }, + "scheduledTask.3.taskForm.relatedTo": { + "links": { + "self": "http://localhost:4201/drafts/CardDef/992531db-9912-49eb-9b72-f710cbcdb085" + } } }, "meta": { diff --git a/packages/drafts-realm/sale-hub.gts b/packages/drafts-realm/sale-hub.gts index 5316a7925d..631e692d62 100644 --- a/packages/drafts-realm/sale-hub.gts +++ b/packages/drafts-realm/sale-hub.gts @@ -969,8 +969,8 @@ class IsolatedSecForSaleHub extends Component { }, {}); const sortedMonths = Object.keys(groupedTasks).sort((a, b) => { - if (a === 'Upcoming & Overdue') return -1; - if (b === 'Upcoming & Overdue') return 1; + if (a === 'Upcoming') return -1; + if (b === 'Overdue') return 1; return new Date(a).getTime() - new Date(b).getTime(); }); @@ -990,15 +990,18 @@ class IsolatedSecForSaleHub extends Component { @action formatDueDate(date: Date) { const todayDate = new Date(); - if (date > todayDate || isToday(date)) - return formatDate(new Date(date), 'MMMM yyyy'); - return 'Upcoming & Overdue'; + + if (isToday(date)) return formatDate(new Date(date), 'MMMM yyyy'); + if (date > todayDate) return 'Upcoming'; + return 'Overdue'; } @action formatDay(date: Date) { const todayDate = new Date(); if (isToday(date)) return 'Today'; if (isTomorrow(date)) return 'Tomorrow'; + if (date > todayDate || isTomorrow(date)) + return formatDate(new Date(date), 'dd/MM/yyyy'); if (date < todayDate) return null; if (isThisMonth(date)) return 'This Month'; return null; From 28b0df95e912a50b4a3607b2e4c4fd9135e56656 Mon Sep 17 00:00:00 2001 From: lucas Date: Sun, 30 Jun 2024 11:57:45 +0800 Subject: [PATCH 08/17] add leadOwner field to sale-hub & lead-form --- .../ca5a2eb1-fb9e-4467-a7aa-54f3ec316ee9.json | 7 + .../fe2e7e4c-796c-4a59-93a0-c2463a10ffdc.json | 9 +- .../e65ae338-64a1-4a97-9ae8-9b9e773a7095.json | 19 + .../bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json | 30 +- packages/drafts-realm/lead-form.gts | 15 + packages/drafts-realm/sale-hub.gts | 769 +++++------------- 6 files changed, 281 insertions(+), 568 deletions(-) create mode 100644 packages/drafts-realm/MatrixUser/e65ae338-64a1-4a97-9ae8-9b9e773a7095.json diff --git a/packages/drafts-realm/LeadForm/ca5a2eb1-fb9e-4467-a7aa-54f3ec316ee9.json b/packages/drafts-realm/LeadForm/ca5a2eb1-fb9e-4467-a7aa-54f3ec316ee9.json index e2a4177ed9..6d9605a9b3 100644 --- a/packages/drafts-realm/LeadForm/ca5a2eb1-fb9e-4467-a7aa-54f3ec316ee9.json +++ b/packages/drafts-realm/LeadForm/ca5a2eb1-fb9e-4467-a7aa-54f3ec316ee9.json @@ -32,6 +32,13 @@ "industry": "Banking", "thumbnailURL": null }, + "relationships": { + "leadOwner": { + "links": { + "self": "../MatrixUser/e65ae338-64a1-4a97-9ae8-9b9e773a7095" + } + } + }, "meta": { "adoptsFrom": { "module": "../lead-form", diff --git a/packages/drafts-realm/LeadForm/fe2e7e4c-796c-4a59-93a0-c2463a10ffdc.json b/packages/drafts-realm/LeadForm/fe2e7e4c-796c-4a59-93a0-c2463a10ffdc.json index 28c0265218..77861ac1aa 100644 --- a/packages/drafts-realm/LeadForm/fe2e7e4c-796c-4a59-93a0-c2463a10ffdc.json +++ b/packages/drafts-realm/LeadForm/fe2e7e4c-796c-4a59-93a0-c2463a10ffdc.json @@ -32,6 +32,13 @@ "industry": "Banking", "thumbnailURL": null }, + "relationships": { + "leadOwner": { + "links": { + "self": "../CardDef/7298c9c6-2964-41a0-87f7-42127a16e12b" + } + } + }, "meta": { "adoptsFrom": { "module": "../lead-form", @@ -39,4 +46,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/drafts-realm/MatrixUser/e65ae338-64a1-4a97-9ae8-9b9e773a7095.json b/packages/drafts-realm/MatrixUser/e65ae338-64a1-4a97-9ae8-9b9e773a7095.json new file mode 100644 index 0000000000..0d8ef73d75 --- /dev/null +++ b/packages/drafts-realm/MatrixUser/e65ae338-64a1-4a97-9ae8-9b9e773a7095.json @@ -0,0 +1,19 @@ +{ + "data": { + "type": "card", + "attributes": { + "username": "law bom bom", + "email": { + "value": "lawbombom@gmail.com" + }, + "description": null, + "thumbnailURL": null + }, + "meta": { + "adoptsFrom": { + "module": "../matrix-user", + "name": "MatrixUser" + } + } + } +} \ No newline at end of file diff --git a/packages/drafts-realm/SaleHub/bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json b/packages/drafts-realm/SaleHub/bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json index 84cf186f46..afa26e92ed 100644 --- a/packages/drafts-realm/SaleHub/bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json +++ b/packages/drafts-realm/SaleHub/bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json @@ -12,7 +12,7 @@ "title": "Lead Form - lawbombom", "website": "lawbom.com", "description": "this is lead-form", - "leadStatus": "Unqualified", + "leadStatus": "Qualified", "phone": "010381997", "email": "lawbombom@gmail.com", "addressInfo": { @@ -120,7 +120,7 @@ "subject": "task b", "dueDate": "2024-06-27", "comments": null, - "isCompleted": false + "isCompleted": true } }, { @@ -140,13 +140,29 @@ "comments": null, "isCompleted": false } + }, + { + "taskForm": { + "taskId": null, + "subject": null, + "dueDate": "2024-06-30", + "comments": null, + "isCompleted": false + } } ], + "convertedStatus": "Qualified", + "isLeadFormConverted": true, "title": null, "description": null, "thumbnailURL": null }, "relationships": { + "leadForm.leadOwner": { + "links": { + "self": "http://localhost:4201/drafts/MatrixUser/e65ae338-64a1-4a97-9ae8-9b9e773a7095" + } + }, "accountForm.owner": { "links": { "self": null @@ -186,6 +202,16 @@ "links": { "self": "http://localhost:4201/drafts/CardDef/992531db-9912-49eb-9b72-f710cbcdb085" } + }, + "scheduledTask.4.taskForm.relatedTo": { + "links": { + "self": null + } + }, + "recordOwner": { + "links": { + "self": "../MatrixUser/e65ae338-64a1-4a97-9ae8-9b9e773a7095" + } } }, "meta": { diff --git a/packages/drafts-realm/lead-form.gts b/packages/drafts-realm/lead-form.gts index f7c3776133..6b0daa4b00 100644 --- a/packages/drafts-realm/lead-form.gts +++ b/packages/drafts-realm/lead-form.gts @@ -4,6 +4,7 @@ import { FieldDef, contains, field, + linksTo, } from 'https://cardstack.com/base/card-api'; import StringField from 'https://cardstack.com/base/string'; import NumberField from 'https://cardstack.com/base/number'; @@ -17,6 +18,7 @@ import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; import { BoxelInput } from '@cardstack/boxel-ui/components'; import { CurrencyAmount } from './currency-amount'; +import { MatrixUser } from './matrix-user'; interface CategorySignature { name: string; @@ -53,6 +55,10 @@ class IsolatedSecForLeadForm extends Component { <@fields.leadStatus />
+
+ + <@fields.leadOwner /> +
<@fields.description /> @@ -178,6 +184,9 @@ class ViewSecForLeadForm extends Component { <@fields.leadStatus @format='edit' /> + + <@fields.leadOwner /> +
@@ -385,6 +394,10 @@ class EditSecFoLeadForm extends Component { + + <@fields.leadOwner /> + + <@fields.phone /> @@ -473,6 +486,7 @@ export class LeadFormField extends FieldDef { @field leadStatus = contains(StringField, { description: `Lead Status`, }); + @field leadOwner = linksTo(MatrixUser); @field phone = contains(StringField, { description: `User's phone number`, }); @@ -522,6 +536,7 @@ export class LeadForm extends CardDef { @field leadStatus = contains(StringField, { description: `Lead Status`, }); + @field leadOwner = linksTo(MatrixUser); @field phone = contains(StringField, { description: `User's phone number`, }); diff --git a/packages/drafts-realm/sale-hub.gts b/packages/drafts-realm/sale-hub.gts index 631e692d62..39b5118e9c 100644 --- a/packages/drafts-realm/sale-hub.gts +++ b/packages/drafts-realm/sale-hub.gts @@ -18,8 +18,10 @@ import GlimmerComponent from '@glimmer/component'; import { BoxelInput, BoxelSelect, + Button, CardContainer, FieldContainer, + GridContainer, IconButton, Modal, } from '@cardstack/boxel-ui/components'; @@ -36,6 +38,7 @@ import { import { OpportunityFormField } from './opportunity-form'; import { LeadFormField } from './lead-form'; import { ContactFormField } from './contact-form'; +import { MatrixUser } from './matrix-user'; interface StepSignature { step: number; @@ -50,6 +53,7 @@ interface StepsSignature { Args: { steps: StepSignature[]; updateLeadStatus: (arg0: string) => void; + isLeadFormConverted: boolean | undefined; handleConvert: () => void; }; } @@ -75,486 +79,6 @@ interface GroupedTasksSignature { isCompleted: boolean; } -//*lead-form -// class IsolatedSecForLeadForm extends Component { -// get getFormattedNoOfEmployees() { -// if (!this.args.model.noOfEmployees) return null; -// return Math.round(this.args.model.noOfEmployees); -// } - -// -// } - -// class ViewSecForLeadForm extends Component { -// -// } - -// class EditSecFoLeadForm extends Component { -// /* Lead Status Options */ -// get selectedLeadStatus() { -// return { name: this.args.model.leadStatus || 'None' }; -// } - -// @tracked leadStatusOptions = [ -// { name: 'None' }, -// { name: 'New' }, -// { name: 'Working' }, -// { name: 'Nurturing' }, -// { name: 'Qualified' }, -// { name: 'Unqualified' }, -// ] as Array; - -// @action updateLeadStatus(type: { name: string }) { -// this.args.model.leadStatus = type.name; -// } - -// /* No Of Employees */ -// @action updateNoOfEmployees(val: number) { -// this.args.model.noOfEmployees = val; -// } - -// get getFormattedNoOfEmployees() { -// if (!this.args.model.noOfEmployees) return null; -// return Math.round(this.args.model.noOfEmployees); -// } - -// /* Lead Source Options */ -// get selectedLeadSource() { -// return { name: this.args.model.leadSource || 'None' }; -// } - -// @tracked leadSourceOptions = [ -// { name: 'None' }, -// { name: 'Advertisement' }, -// { name: 'Employee Referral' }, -// { name: 'External Referral' }, -// { name: 'Partner' }, -// { name: 'Public Relations' }, -// { name: 'Seminar - Internal' }, -// { name: 'Seminar - Partner' }, -// { name: 'Trade Show' }, -// { name: 'Web' }, -// { name: 'Word of mouth' }, -// { name: 'Other' }, -// ] as Array; - -// @action updateLeadSource(type: { name: string }) { -// this.args.model.leadSource = type.name; -// } - -// /* Industry Options */ -// get selectedIndustry() { -// return { name: this.args.model.industry || 'None' }; -// } - -// @tracked industryOptions = [ -// { name: 'None' }, -// { name: 'Agriculture' }, -// { name: 'Apparel' }, -// { name: 'Banking' }, -// { name: 'Biotechnology' }, -// { name: 'Chemicals' }, -// { name: 'Communications' }, -// { name: 'Construction' }, -// { name: 'Consulting' }, -// { name: 'Education' }, -// { name: 'Electronics' }, -// { name: 'Energy' }, -// { name: 'Engineering' }, -// { name: 'Entertainment' }, -// { name: 'Environmental' }, -// { name: 'Finance' }, -// { name: 'Food & Beverage' }, -// { name: 'Government' }, -// { name: 'Healthcare' }, -// { name: 'Hospitality' }, -// { name: 'Insurance' }, -// { name: 'Machinery' }, -// { name: 'Manufacturing' }, -// { name: 'Media' }, -// { name: 'Not For Profit' }, -// { name: 'Recreation' }, -// { name: 'Retail' }, -// { name: 'Shipping' }, -// { name: 'Technology' }, -// { name: 'Telecommunications' }, -// { name: 'Transportation' }, -// { name: 'Utilities' }, -// { name: 'Others' }, -// ] as Array; - -// @action updateIndustry(type: { name: string }) { -// this.args.model.industry = type.name; -// } - -// -// } - -// class LeadForm extends FieldDef { -// static displayName = 'Lead Form'; - -// @field name = contains(UserName, { -// description: `User's Full Name`, -// }); -// @field company = contains(StringField, { -// description: `User's Company Name`, -// }); -// @field title = contains(StringField, { -// description: `User's Title`, -// }); -// @field website = contains(StringField, { -// description: `User's Website`, -// }); -// @field description = contains(MarkdownField, { -// description: `User's Description`, -// }); -// @field leadStatus = contains(StringField, { -// description: `Lead Status`, -// }); -// @field phone = contains(StringField, { -// description: `User's phone number`, -// }); -// @field email = contains(UserEmail, { -// description: `User's Email`, -// }); -// @field addressInfo = contains(AddressInfo, { -// description: `User's AddressInfo`, -// }); -// @field noOfEmployees = contains(NumberField, { -// description: `No Of Employees`, -// }); -// @field annualRevenue = contains(CurrencyAmount, { -// description: `Annual Revenue`, -// }); -// @field leadSource = contains(StringField, { -// description: `Lead Source`, -// }); -// @field industry = contains(StringField, { -// description: `Industry`, -// }); - -// static isolated = IsolatedSecForLeadForm; -// static atom = ViewSecForLeadForm; -// static embedded = ViewSecForLeadForm; -// static edit = EditSecFoLeadForm; -// } - //*task-form class TaskForm extends FieldDef { static displayName = 'Task Form'; @@ -586,11 +110,17 @@ class Steps extends GlimmerComponent { @tracked currentStepIndex = this.steps[0].step || 0; get isStepCompleted() { - return this.currentStepStatus === 'Step Completed'; + return ( + this.currentStepStatus === 'Step Completed' || + this.args.isLeadFormConverted + ); } get currentStepStatus() { - if (this.currentStepIndex >= this.steps.length - 1) return 'Convert'; + if (this.currentStepIndex >= this.steps.length - 1) { + if (this.args.isLeadFormConverted) return 'Converted'; + return 'Convert'; + } if (this.steps[this.currentStepIndex].isCompleted) return 'Step Completed'; return 'Mark Status as Complete'; } @@ -923,7 +453,9 @@ class IsolatedSecForSaleHub extends Component { ); this.initStepOptions.forEach((option, index) => { - option.isCompleted = index <= currentCompletedStep; + option.isCompleted = + index <= currentCompletedStep || + this.args.model.leadForm?.leadStatus === 'Qualified'; option.isProceedToNextStep = index === currentCompletedStep + 1; }); @@ -1021,6 +553,41 @@ class IsolatedSecForSaleHub extends Component { return this.args.model.scheduledTask; } + /* Converted Status Options */ + get selectedConvertedStatus() { + return { + name: + this.args.model.convertedStatus || + this.convertedStatusOptions[0].name || + 'None', + }; + } + + @tracked convertedStatusOptions = [ + { name: 'Qualified' }, + { name: 'Unqualified' }, + ] as Array; + + @action updateConvertedStatus(type: { name: string }) { + this.args.model.convertedStatus = type.name; + + if (this.args.model.leadForm) { + this.args.model.leadForm.leadStatus = type.name; + } + } + + @action cancel() { + this.args.model.isLeadFormConverted = + this.args.model.isLeadFormConverted || false; + + this.closeModal(); + } + + @action convert() { + this.args.model.isLeadFormConverted = true; + this.closeModal(); + } + @@ -1314,10 +929,34 @@ class IsolatedSecForSaleHub extends Component { export class SaleHub extends CardDef { static displayName = 'sale hub'; - @field leadForm = contains(LeadFormField); - @field accountForm = contains(CrmAccountField); - @field contactForm = contains(ContactFormField); - @field opportunityForm = contains(OpportunityFormField); - @field scheduledTask = containsMany(ScheduledTask); + + @field targetPage = contains(StringField, { + description: `Show which page is clicked`, + }); + @field leadForm = contains(LeadFormField, { + description: `Lead form`, + }); + @field accountForm = contains(CrmAccountField, { + description: `crm account`, + }); + @field contactForm = contains(ContactFormField, { + description: `Contact Form`, + }); + @field opportunityForm = contains(OpportunityFormField, { + description: `Opportunity Form`, + }); + @field scheduledTask = containsMany(ScheduledTask, { + description: `Upcoming & overdue tasks`, + }); + @field recordOwner = linksTo(MatrixUser, { + description: `Record owner`, + }); + @field convertedStatus = contains(StringField, { + description: `Converted Status`, + }); + @field isLeadFormConverted = contains(BooleanField, { + description: `Check if leadForm is converted`, + }); + static isolated = IsolatedSecForSaleHub; } From c45beb7e458281c07e298aa742d40198e67602a5 Mon Sep 17 00:00:00 2001 From: lucas Date: Sun, 30 Jun 2024 17:20:02 +0800 Subject: [PATCH 09/17] create targetPage tab logic for each converted form --- .../bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json | 1 + packages/drafts-realm/sale-hub.gts | 116 +++++++++++++++++- 2 files changed, 113 insertions(+), 4 deletions(-) diff --git a/packages/drafts-realm/SaleHub/bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json b/packages/drafts-realm/SaleHub/bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json index afa26e92ed..57dfdb6fe6 100644 --- a/packages/drafts-realm/SaleHub/bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json +++ b/packages/drafts-realm/SaleHub/bcbf2e7d-16ad-41e0-95a4-03dc988809e5.json @@ -2,6 +2,7 @@ "data": { "type": "card", "attributes": { + "targetPage": "Account Form", "leadForm": { "name": { "salutation": "Mr.", diff --git a/packages/drafts-realm/sale-hub.gts b/packages/drafts-realm/sale-hub.gts index 39b5118e9c..6f2a65a098 100644 --- a/packages/drafts-realm/sale-hub.gts +++ b/packages/drafts-realm/sale-hub.gts @@ -40,6 +40,21 @@ import { LeadFormField } from './lead-form'; import { ContactFormField } from './contact-form'; import { MatrixUser } from './matrix-user'; +interface TargetPageLinkSingnature { + name: string; + isActive: boolean; + shouldShowFormData: boolean; +} + +interface TargetPageLinksSignature { + Element: HTMLElement; + Args: { + targetPageLinks: TargetPageLinkSingnature[]; + targetPage: string | undefined; + onSelectPage: (val: string) => void; + }; +} + interface StepSignature { step: number; name: string; @@ -103,6 +118,39 @@ class TaskForm extends FieldDef { }); } +//*Target Page Links +class TargetPageLinks extends GlimmerComponent { + +} + //*steps class Steps extends GlimmerComponent { @tracked steps = this.args.steps.map((step) => ({ ...step })); @@ -408,7 +456,52 @@ class IsolatedSecForSaleHub extends Component { return `${firstName} ${leadForm.company}`; } - //step + //targetPageLinks + @tracked initTargetPageLinks = [ + { + name: 'Lead Form', + isActive: true, + shouldShowFormData: true, + }, + { + name: 'Account Form', + isActive: false, + shouldShowFormData: false, + }, + { + name: 'Contact Form', + isActive: false, + shouldShowFormData: false, + }, + { + name: 'Opportunity Form', + isActive: false, + shouldShowFormData: false, + }, + ] as Array; + + get targetPageLinks() { + return this.initTargetPageLinks.map((page) => { + return { ...page, isActive: page.name === this.targetPage }; + }); + } + + get targetPage() { + return this.args.model.targetPage; + } + + @action onSelectPage(name: string) { + this.args.model.targetPage = name; + + this.initTargetPageLinks = this.initTargetPageLinks.map((page) => { + return { + ...page, + isActive: page.name === name, + }; + }); + } + + //steps @tracked initStepOptions = [ { step: 0, @@ -696,9 +789,16 @@ class IsolatedSecForSaleHub extends Component { -
- +
+ +
+ +
@@ -242,11 +242,12 @@ class ViewSecForLeadForm extends Component { overflow: hidden; } .field-group-title { - font-size: var(--boxel-font-size); - font-weight: 800; - margin-bottom: var(--boxel-sp-xs); + font-size: 1rem; + font-weight: bold; + margin-bottom: 0.75rem; } .field-input-group { + overflow: overlay; display: flex; flex-direction: column; justify-content: space-evenly; @@ -254,7 +255,7 @@ class ViewSecForLeadForm extends Component { background-color: #fbfbfb; border: 1px solid var(--boxel-300); border-radius: var(--boxel-border-radius); - padding: var(--boxel-sp); + padding: var(--boxel-sp-xxs); } diff --git a/packages/drafts-realm/opportunity-form.gts b/packages/drafts-realm/opportunity-form.gts index a4abc9addb..23d9803da5 100644 --- a/packages/drafts-realm/opportunity-form.gts +++ b/packages/drafts-realm/opportunity-form.gts @@ -34,6 +34,19 @@ const formatNumber = (val: number) => { }; /* Amount */ + +class EmbeddedSecForAmount extends Component { + +} + class EditSecForAmount extends Component { @tracked currencyOptions = ['Select', 'RM']; get selectedCurrency() { @@ -114,6 +127,7 @@ class AmountField extends FieldDef { description: `Total Amount`, }); + static embedded = EmbeddedSecForAmount; static edit = EditSecForAmount; } @@ -174,7 +188,6 @@ class IsolatedSecForOpportunityForm extends Component {
-
<@fields.accountName /> @@ -341,27 +354,27 @@ class EditSecForOpportunityForm extends Component {