Last active
February 27, 2019 08:00
-
-
Save robmazan/a104e33416b5cc36ccb709b764b574e9 to your computer and use it in GitHub Desktop.
Lead Changes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Lead Changes | |
Lead creation | |
ADAM batch upload -> NOT_YET_ISSUED | |
NOT_YET_ISSUED | |
Start campaign -> OPEN_AWAITING_ASSIGNMENT | |
Closed by ADAM -> CLOSED | |
OPEN_AWAITING_ASSIGNMENT | |
Assign -> OPEN_AWAITING_ACTION | |
Closed by ADAM -> CLOSED | |
Lead expired -> CLOSED | |
OPEN_AWAITING_ACTION | |
Take action -> OPEN_AWAITING_ACTION | |
Reassign -> OPEN_AWAITING_ACTION | |
Deassign -> OPEN_AWAITING_ASSIGNMENT | |
Create new lead -> CLOSED | |
Create new lead for myself -> CLOSED | |
Close lead -> CLOSED | |
Closed by ADAM -> CLOSED | |
Lead expired -> CLOSED | |
CLOSED |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const logs = []; | |
let lastEvent = null; | |
let contactAttempts = 0; | |
let contactAttemptFreeze = false; | |
const radioStyle = { | |
fontFamily: 'Arial', | |
margin: '5px', | |
padding: '10px', | |
boxShadow: '1px 1px 2px', | |
borderRadius: '5px', | |
background: 'lightblue' | |
}; | |
const buttonStyle = { | |
padding: '10px', | |
margin: '5px', | |
borderRadius: '5px', | |
border: 0, | |
boxShadow: '1px 1px 2px', | |
backgroundColor: 'lightblue', | |
fontFamily: 'Arial' | |
}; | |
const Button = props => <button style={buttonStyle} {...props}>{props.children}</button>; | |
const logLead = (props, message = 'Lead updated:') => { | |
logs.push(message + ' ' + JSON.stringify(props)); | |
}; | |
const logAction = (props, message = 'Lead action recorded: ') => { | |
logs.push(message + ' ' + JSON.stringify(Object.assign({}, props, { creationTime: 'NOW' }))); | |
}; | |
const logT24 = message => { | |
logs.push(`T24 sync, message: ${message}`); | |
} | |
const contact = (increase = true, msg = 'Contact attempt increased') => { | |
if (contactAttemptFreeze) { | |
logs.push('Contact attempt counter frozen'); | |
return; | |
} | |
if (increase) { | |
contactAttempts++; | |
} | |
logs.push(msg); | |
}; | |
const contactFreeze = () => { | |
contactAttemptFreeze = true; | |
logs.push('Contact attempt counter frozen'); | |
}; | |
const contactReset = () => { | |
contactAttempts = 0; | |
contactAttemptFreeze = false; | |
logs.push('Contact attempt counter reset'); | |
}; | |
const clearLogs = () => logs.length = 0; | |
const adamCloseHandler = model => () => { | |
const closeMsg = 'Lead <Lead.ID> Closed by ADAM <action date+time>.'; | |
clearLogs(); | |
logLead({ | |
status: 'CLOSED', | |
closeReason: closeMsg, | |
closeTime: 'NOW' | |
}); | |
logAction({ | |
actionType: 'CLOSE_LEAD', | |
outcomeNote: closeMsg | |
}); | |
logT24(closeMsg); | |
lastLog = 'Closed by ADAM'; | |
model.emit(lastLog); | |
} | |
const states = { | |
'Lead creation': { | |
render({ model }) { | |
const batchUpload = () => { | |
clearLogs(); | |
logLead({ status: 'NOT_YET_ISSUED', creationTime: 'NOW' }, `Lead records created: `); | |
logAction({ actionType: 'NEW_LEAD_CREATED', outcomeNote: '???' }); | |
contactReset(); | |
lastEvent = 'ADAM batch upload'; | |
model.emit(lastEvent); | |
}; | |
return <div> | |
<Button onClick={batchUpload}>ADAM batch upload</Button> | |
</div>; | |
} | |
}, | |
'NOT_YET_ISSUED': { | |
render({ model }) { | |
const campaignAllocation = () => { | |
const note = 'Lead <Lead.ID> for Campaign <Campaign.name> opened as campaign started, allocated to <teamAllocation>, <action date+time>.'; | |
clearLogs(); | |
logLead({ | |
status: 'OPEN_AWAITING_ASSIGNMENT', | |
teamAllocation: 'as campaign manager set' | |
}); | |
logAction({ | |
actionType: 'STATUS_CHANGE', | |
outcomeNote: note | |
}, 'Lead action recorded for each lead: '); | |
logT24(note); | |
lastLog = 'Start campaign'; | |
model.emit(lastLog); | |
}; | |
return <div> | |
<Button onClick={campaignAllocation}>Campaign manager allocates leads and starts campaign</Button> | |
<Button onClick={adamCloseHandler(model)}>ADAM closes lead</Button> | |
</div>; | |
} | |
}, | |
'OPEN_AWAITING_ASSIGNMENT': { | |
render({ model }) { | |
const assign = () => { | |
const note = 'Lead <Lead.ID> assigned to <assignee>, <action date+time>.'; | |
clearLogs(); | |
logLead({ | |
status: 'OPEN_AWAITING_ACTION', | |
assignee: 'as set' | |
}); | |
logAction({ | |
actionType: 'ASSIGNED', | |
outcomeNote: note | |
}); | |
logT24(note); | |
lastEvent = 'Assign'; | |
model.emit(lastEvent); | |
}; | |
return <div> | |
<Button onClick={assign}>Team manager assigns lead to team member</Button> | |
<Button onClick={assign}>Team member assigns lead to herself</Button> | |
<Button onClick={adamCloseHandler(model)}>ADAM closes lead</Button> | |
</div>; | |
} | |
}, | |
'OPEN_AWAITING_ACTION': { | |
render({ model, state, setState }) { | |
let formEl; | |
const setFormRef = el => formEl = el; | |
const processSaveAndLog = () => { | |
clearLogs(); | |
// the "action" can be CALL_BACK only, so not checking that | |
logLead({ | |
callbackDate: 'as set' | |
}); | |
let note; | |
switch (formEl.contactType.value) { | |
case 'contactMade': | |
note = '<Lead.ID> customer reached by <assignee> Call Back date <callbackDate> for the reason <outcomeNote>, <action date+time>.'; | |
logAction({ | |
actionType: 'CONVERSED_WITH_CUSTOMER', | |
outcomeNote: note | |
}); | |
logT24(note); | |
contact(); | |
contactFreeze(); | |
break; | |
case 'noContact': | |
note = '<Lead.ID> No contact by <assignee> Call Back date <callbackDate> for the reason <outcomeNote>, Contact follow up sent <contactFollowUp>, <action date+time>.'; | |
logAction({ | |
actionType: 'FAILED_TO_REACH_CUSTOMER', | |
outcomeNote: note | |
}); | |
logT24(note); | |
contact(); | |
break; | |
case 'inbound': | |
note = '<Lead.ID> Incoming call taken by <assignee> Call Back date <callbackDate> for the reason <outcomeNote>, <action date+time>.'; | |
logAction({ | |
actionType: 'INBOUND_CALL', | |
outcomeNote: note | |
}); | |
logT24(note); | |
contactFreeze(); | |
break; | |
} | |
lastEvent = 'Save & log lead'; | |
model.emit('Take action'); | |
}; | |
const create = ({ forMyself, closeOldLead }) => { | |
clearLogs(); | |
logLead({ | |
status: forMyself ? 'OPEN_AWAITING_ACTION': 'OPEN_AWAITING_ASSIGNMENT', | |
parentLead: 'ID of the parent lead', | |
callbackDate: 'as entered', | |
assignee: 'as (and if) set', | |
teamAllocation: 'as (and if) set' | |
}, 'New lead record created with data copied over from parent lead: '); | |
logAction({ | |
lead: 'Old Lead.ID', | |
actionType: 'NEW_LEAD_CREATED', | |
outcomeNote: `New lead created. New lead ID: <New lead's ID>, status: ${forMyself ? 'OPEN_AWAITING_ACTION': 'OPEN_AWAITING_ASSIGNMENT'}` | |
}, 'Lead action recorded for the old lead: '); | |
if (closeOldLead) { | |
logLead({ | |
status: 'CLOSED', | |
closeReason: '???', | |
closeDetails: '???', | |
closeTime: 'NOW' | |
}, 'Closing old lead: '); | |
logAction({ | |
lead: 'Old Lead.ID', | |
actionType: 'CLOSE_LEAD', | |
outcomeNote: `Lead closed by <USER> for the reason <old lead's outcomeNote>` | |
}, 'Lead action recorded for the old lead: '); | |
} | |
const newLeadNote = 'New Lead <Lead.ID> created from <parentLeadID> for the reason <outcomeNote>, <action date+time>.'; | |
logAction({ | |
lead: 'New Lead.ID', | |
actionType: 'NEW_LEAD_CREATED', | |
outcomeNote: newLeadNote | |
}, 'Lead action recorded for the new lead: '); | |
logT24(newLeadNote); | |
lastEvent = forMyself ? 'Create new lead for myself': 'Create new lead'; | |
model.emit(lastEvent); | |
}; | |
const processClose = () => { | |
if (formEl.closeReason.value === 'CREATED_OTHER_LEADS') { | |
create({ | |
forMyself: formEl.assignTo.value === 'myself', | |
closeOldLead: formEl.closeAfterCreate.checked | |
}); | |
return; | |
} | |
clearLogs(); | |
const note = 'Lead <Lead.ID> Closed by <Actioner> for the reason <closeReason> [,Optional: <outcomeNote>], <action date+time>.'; | |
logAction({ | |
actionType: 'CLOSE_LEAD', | |
outcomeNote: 'as set' | |
}); | |
logLead({ | |
status: 'CLOSED', | |
closeReason: formEl.closeReason.value, | |
closeDetails: note, | |
closeTime: 'NOW' | |
}); | |
logT24(note); | |
contactFreeze(); | |
lastEvent = 'Action saved & lead closed'; | |
model.emit('Close lead'); | |
}; | |
const reAssign = () => { | |
clearLogs(); | |
logLead({ | |
assignee: 'New assignee' | |
}); | |
const note = '<Lead.ID> assigned by <Actioner> to <assignee>, <action date+time>.'; | |
logAction({ | |
actionType: 'ASSIGNED', | |
outcomeNote: note | |
}); | |
logT24(note); | |
model.emit('Reassign'); | |
}; | |
const deAssign = () => { | |
clearLogs(); | |
logLead({ | |
assignee: 'NULL', | |
status: 'OPEN_AWAITING_ASSIGNMENT' | |
}); | |
const note = '<Lead.ID> De-assigned from <assignee> by <Actioner>, <action date+time>.'; | |
logAction({ | |
actionType: 'STATUS_CHANGE', | |
outcomeNote: note | |
}); | |
logT24(note); | |
model.emit('Deassign'); | |
}; | |
const leadExpires = () => { | |
clearLogs(); | |
logLead({ | |
status: 'CLOSED' | |
}); | |
const note = 'Lead <Lead.ID> Closed as expired <action date+time>.'; | |
logAction({ | |
actionType: 'CLOSE_LEAD', | |
outcomeNote: note | |
}); | |
logT24(note); | |
model.emit('Lead expired'); | |
}; | |
const updateContactType = (ev) => { | |
setState({ contactType: ev.target.value }); | |
}; | |
const updateAssignTo = (ev) => { | |
setState({ assignTo: ev.target.value }); | |
}; | |
const updateCloseAfterCreate = (ev) => { | |
setState({ closeAfter: ev.target.value }); | |
}; | |
return <form ref={setFormRef}> | |
<p> | |
<Button onClick={reAssign}>Re-assign lead</Button> | |
<Button onClick={deAssign}>De-assign lead</Button> | |
<Button onClick={adamCloseHandler(model)}>ADAM closes lead</Button> | |
<Button onClick={leadExpires}>Lead expires</Button> | |
</p> | |
<p> | |
<label style={radioStyle}> | |
<input type="radio" name="contactType" value="contactMade" | |
checked={state.contactType === 'contactMade'} | |
onChange={updateContactType} /> | |
Contact made | |
</label> | |
<label style={radioStyle}> | |
<input type="radio" name="contactType" value="noContact" | |
checked={state.contactType === 'noContact'} | |
onChange={updateContactType} /> | |
No contact | |
</label> | |
<label style={radioStyle}> | |
<input type="radio" name="contactType" value="inbound" | |
checked={state.contactType === 'inbound'} | |
onChange={updateContactType} /> | |
Inbound | |
</label> | |
</p> | |
<p style={{lineHeight: '3em'}}> | |
In case of "Create/Refer new lead", assign new lead to: | |
<label style={radioStyle}> | |
<input type="radio" name="assignTo" value="myself" | |
checked={state.assignTo === 'myself'} | |
onChange={updateAssignTo} /> | |
Myself | |
</label> | |
<label style={radioStyle}> | |
<input type="radio" name="assignTo" value="team" | |
checked={state.assignTo === 'team'} | |
onChange={updateAssignTo} /> | |
Team | |
</label> | |
<label style={radioStyle}> | |
<input type="checkbox" name="closeAfterCreate" | |
checked={state.closeAfterCreate} | |
onChange={updateCloseAfterCreate} /> | |
Close after create | |
</label> | |
</p> | |
<label> | |
Save & log lead: | |
<select name="saveAndLogAction" onChange={processSaveAndLog}> | |
<option></option> | |
<option value="CALL_BACK">Call back date</option> | |
</select> | |
</label> | |
| |
<label> | |
Action saved & lead closed: | |
<select name="closeReason" onChange={processClose}> | |
<option></option> | |
<option value="APPLICATION_COMPLETED" disabled={state.contactType === 'noContact'}> | |
Application completed | |
</option> | |
<option value="MEETING_SCHEDULED" disabled={state.contactType === 'noContact'}> | |
Meeting scheduled | |
</option> | |
<option value="CATCH_UP_COMPLETED" disabled={state.contactType === 'noContact'}> | |
Catch Up completed on call | |
</option> | |
<option value="CREATED_OTHER_LEADS" disabled={state.contactType === 'noContact'}> | |
Create/Refer new lead | |
</option> | |
<option value="NO_SALE" disabled={state.contactType === 'noContact'}> | |
No sale | |
</option> | |
<option value="LEAD_INAPPROPRIATE" disabled={state.contactType === 'noContact'}> | |
Lead inapropriate for customer | |
</option> | |
<option value="INVALID_CUSTOMER_DETAILS" disabled={state.contactType === 'noContact' || state.contactType === 'inbound'}> | |
Invalid customer details | |
</option> | |
<option value="UNABLE_TO_REACH_CUSTOMER" disabled={state.contactType === 'contactMade' || state.contactType === 'inbound'}> | |
Unable to reach customer | |
</option> | |
</select> | |
</label> | |
</form>; | |
} | |
}, | |
'CLOSED': { | |
render({ model }) { | |
return <div></div>; | |
} | |
}, | |
}; | |
const LogDisplay = ({ items }) => { | |
return <div> | |
<h2>{lastEvent}</h2> | |
<ol> | |
{items.map(item => <li>{item}</li>)} | |
</ol> | |
</div>; | |
}; | |
const render = model => { | |
const currentStateName = model.active_states[0].name; | |
const stateStyle = { | |
background: 'aliceblue', | |
padding: '10px', | |
borderRadius: '5px', | |
boxShadow: '0 0 5px', | |
margin: '5px', | |
height: '350px', | |
overflow: 'auto' | |
}; | |
class StateDisplay extends React.Component { | |
constructor(props) { | |
super(props); | |
this.state = { | |
contactType: 'contactMade', | |
assignTo: 'myself' | |
}; | |
} | |
render() { | |
return states[this.props.stateName].render({ | |
model: this.props.model, | |
setState: this.setState.bind(this), | |
state: this.state | |
}); | |
}; | |
} | |
return <div style={stateStyle}> | |
<h1> | |
Current state: {model.active_states[0].name} | |
(contact attempts: {contactAttempts}) | |
</h1> | |
<StateDisplay stateName={currentStateName} model={model}/> | |
<hr /> | |
<LogDisplay items={logs} /> | |
</div>; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment