Squashed commit of the following: commit 900400f053 Author: Steven Miers <steven.miers@gmail.com> Date: Thu Sep 3 03:53:06 2015 -0500 VRFS-3359 : Styling, cleanup, more idiomatic react state management for input fields. commit 766842f4e7 Author: Steven Miers <steven.miers@gmail.com> Date: Thu Sep 3 03:52:04 2015 -0500 VRFS-3359 : Teacher pricing * Currency fields only appear when both the appropriate pricing option (column) AND the time range (row). * Currency fields set state and format on blur commit dace5dd08a Author: Steven Miers <steven.miers@gmail.com> Date: Thu Sep 3 02:00:18 2015 -0500 VRFS-3359 : New fields in API output and controller spec. commit 3f2fd94c58 Author: Steven Miers <steven.miers@gmail.com> Date: Thu Sep 3 01:53:28 2015 -0500 VRFS-3359 : Update schema and models with per-month and per-lesson price for each interval. Update tests to match. commit 8ce313f5c7 Author: Steven Miers <steven.miers@gmail.com> Date: Mon Aug 31 23:02:44 2015 -0500 VRFS-3359 : Teacher pricing Display or hide fields depending on checkboxes. commit 005ee8aabd Author: Steven Miers <steven.miers@gmail.com> Date: Mon Aug 31 14:56:25 2015 -0500 VRFS-3359 : Validate end year greater or equal start year. commit 69bb4e469a Author: Steven Miers <steven.miers@gmail.com> Date: Mon Aug 31 13:12:36 2015 -0500 VRFS-3359 : Teacher Experience List Component * Add field label properties to experience list component. Each of the three lists can set labels for title and organization. Date label is inferred from showEndDate property * Update usages of this list * Sort by start_date * List display uses available fields (e.g., date range when available) commit 0878c5fd3f Author: Steven Miers <steven.miers@gmail.com> Date: Mon Aug 31 11:32:20 2015 -0500 VRFS-3359 : Proper setting of experience keys and saving.

This commit is contained in:
Steven Miers 2015-09-03 13:25:29 -05:00
parent 4e40da594c
commit a7fd314683
19 changed files with 391 additions and 206 deletions

View File

@ -1,30 +1,33 @@
CREATE TABLE teachers (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE,
introductory_video VARCHAR(1024) NULL,
years_teaching SMALLINT NOT NULL DEFAULT 0,
years_playing SMALLINT NOT NULL DEFAULT 0,
teaches_age_lower SMALLINT NOT NULL DEFAULT 0,
teaches_age_upper SMALLINT NOT NULL DEFAULT 0,
teaches_beginner BOOLEAN NOT NULL DEFAULT FALSE,
teaches_intermediate BOOLEAN NOT NULL DEFAULT FALSE,
teaches_advanced BOOLEAN NOT NULL DEFAULT FALSE,
website VARCHAR(1024) NULL,
biography VARCHAR(4096) NULL,
prices_per_lesson BOOLEAN NOT NULL DEFAULT FALSE,
prices_per_month BOOLEAN NOT NULL DEFAULT FALSE,
lesson_duration_30 BOOLEAN NOT NULL DEFAULT FALSE,
lesson_duration_45 BOOLEAN NOT NULL DEFAULT FALSE,
lesson_duration_60 BOOLEAN NOT NULL DEFAULT FALSE,
lesson_duration_90 BOOLEAN NOT NULL DEFAULT FALSE,
lesson_duration_120 BOOLEAN NOT NULL DEFAULT FALSE,
price_per_lesson_cents INT NULL,
price_per_month_cents INT NULL,
price_duration_30_cents INT NULL,
price_duration_45_cents INT NULL,
price_duration_60_cents INT NULL,
price_duration_90_cents INT NULL,
price_duration_120_cents INT NULL,
user_id VARCHAR(64) REFERENCES users(id) ON DELETE CASCADE,
introductory_video VARCHAR(1024) NULL,
years_teaching SMALLINT NOT NULL DEFAULT 0,
years_playing SMALLINT NOT NULL DEFAULT 0,
teaches_age_lower SMALLINT NOT NULL DEFAULT 0,
teaches_age_upper SMALLINT NOT NULL DEFAULT 0,
teaches_beginner BOOLEAN NOT NULL DEFAULT FALSE,
teaches_intermediate BOOLEAN NOT NULL DEFAULT FALSE,
teaches_advanced BOOLEAN NOT NULL DEFAULT FALSE,
website VARCHAR(1024) NULL,
biography VARCHAR(4096) NULL,
prices_per_lesson BOOLEAN NOT NULL DEFAULT FALSE,
prices_per_month BOOLEAN NOT NULL DEFAULT FALSE,
lesson_duration_30 BOOLEAN NOT NULL DEFAULT FALSE,
lesson_duration_45 BOOLEAN NOT NULL DEFAULT FALSE,
lesson_duration_60 BOOLEAN NOT NULL DEFAULT FALSE,
lesson_duration_90 BOOLEAN NOT NULL DEFAULT FALSE,
lesson_duration_120 BOOLEAN NOT NULL DEFAULT FALSE,
price_per_lesson_30_cents INT NULL,
price_per_lesson_45_cents INT NULL,
price_per_lesson_60_cents INT NULL,
price_per_lesson_90_cents INT NULL,
price_per_lesson_120_cents INT NULL,
price_per_month_30_cents INT NULL,
price_per_month_45_cents INT NULL,
price_per_month_60_cents INT NULL,
price_per_month_90_cents INT NULL,
price_per_month_120_cents INT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
@ -48,7 +51,7 @@ CREATE TABLE teacher_experiences(
name VARCHAR(200) NOT NULL,
organization VARCHAR(200) NOT NULL,
start_year SMALLINT NOT NULL DEFAULT 0,
end_year SMALLINT NOT NULL DEFAULT 0
end_year SMALLINT NULL
);
-- Has many/through tables:

View File

@ -67,13 +67,16 @@ module JamRuby
teacher.lesson_duration_60 = params[:lesson_duration_60] if params.key?(:lesson_duration_60)
teacher.lesson_duration_90 = params[:lesson_duration_90] if params.key?(:lesson_duration_90)
teacher.lesson_duration_120 = params[:lesson_duration_120] if params.key?(:lesson_duration_120)
teacher.price_per_lesson_cents = params[:price_per_lesson_cents] if params.key?(:price_per_lesson_cents)
teacher.price_per_month_cents = params[:price_per_month_cents] if params.key?(:price_per_month_cents)
teacher.price_duration_30_cents = params[:price_duration_30_cents] if params.key?(:price_duration_30_cents)
teacher.price_duration_45_cents = params[:price_duration_45_cents] if params.key?(:price_duration_45_cents)
teacher.price_duration_60_cents = params[:price_duration_60_cents] if params.key?(:price_duration_60_cents)
teacher.price_duration_90_cents = params[:price_duration_90_cents] if params.key?(:price_duration_90_cents)
teacher.price_duration_120_cents = params[:price_duration_120_cents] if params.key?(:price_duration_120_cents)
teacher.price_per_lesson_30_cents = params[:price_per_lesson_30_cents] if params.key?(:price_per_lesson_30_cents)
teacher.price_per_lesson_45_cents = params[:price_per_lesson_45_cents] if params.key?(:price_per_lesson_45_cents)
teacher.price_per_lesson_60_cents = params[:price_per_lesson_60_cents] if params.key?(:price_per_lesson_60_cents)
teacher.price_per_lesson_90_cents = params[:price_per_lesson_90_cents] if params.key?(:price_per_lesson_90_cents)
teacher.price_per_lesson_120_cents = params[:price_per_lesson_120_cents] if params.key?(:price_per_lesson_120_cents)
teacher.price_per_month_30_cents = params[:price_per_month_30_cents] if params.key?(:price_per_month_30_cents)
teacher.price_per_month_45_cents = params[:price_per_month_45_cents] if params.key?(:price_per_month_45_cents)
teacher.price_per_month_60_cents = params[:price_per_month_60_cents] if params.key?(:price_per_month_60_cents)
teacher.price_per_month_90_cents = params[:price_per_month_90_cents] if params.key?(:price_per_month_90_cents)
teacher.price_per_month_120_cents = params[:price_per_month_120_cents] if params.key?(:price_per_month_120_cents)
# Many-to-many relations:
teacher.genres = params[:genres].collect{|genre_id|Genre.find(genre_id)} if params[:genres].present?

View File

@ -3,7 +3,8 @@ module JamRuby
include HtmlSanitize
html_sanitize strict: [:name, :organization]
belongs_to :teacher, :class_name => "JamRuby::Teacher"
attr_accessible :name, :experience_type, :organization, :start_year, :end_year
scope :teaching, where(experience_type: 'teaching')
scope :education, where(experience_type: 'education')
scope :awards, where(experience_type: 'award')

View File

@ -144,13 +144,16 @@ describe Teacher do
lesson_duration_60: true,
lesson_duration_90: true,
lesson_duration_120: true,
price_per_lesson_cents: 3000,
price_per_month_cents: 3000,
price_duration_30_cents: 3000,
price_duration_45_cents: 3000,
price_duration_60_cents: 3000,
price_duration_90_cents: 3000,
price_duration_120_cents: 3000
price_per_lesson_30_cents: 3000,
price_per_lesson_45_cents: 3000,
price_per_lesson_60_cents: 3000,
price_per_lesson_90_cents: 3000,
price_per_lesson_120_cents: 3000,
price_per_month_30_cents: 5000,
price_per_month_45_cents: 5000,
price_per_month_60_cents: 5000,
price_per_month_90_cents: 5000,
price_per_month_120_cents: 5000
)
teacher.should_not be_nil
@ -165,13 +168,16 @@ describe Teacher do
t.lesson_duration_60.should be_true
t.lesson_duration_90.should be_true
t.lesson_duration_120.should be_true
t.price_per_lesson_cents.should == 3000
t.price_per_month_cents.should == 3000
t.price_duration_30_cents.should == 3000
t.price_duration_45_cents.should == 3000
t.price_duration_60_cents.should == 3000
t.price_duration_90_cents.should == 3000
t.price_duration_120_cents.should == 3000
t.price_per_lesson_30_cents.should == 3000
t.price_per_lesson_45_cents.should == 3000
t.price_per_lesson_60_cents.should == 3000
t.price_per_lesson_90_cents.should == 3000
t.price_per_lesson_120_cents.should == 3000
t.price_per_month_30_cents.should == 5000
t.price_per_month_45_cents.should == 5000
t.price_per_month_60_cents.should == 5000
t.price_per_month_90_cents.should == 5000
t.price_per_month_120_cents.should == 5000
end
end
@ -249,13 +255,11 @@ describe Teacher do
lesson_duration_60: false,
lesson_duration_90: false,
lesson_duration_120: false,
price_per_lesson_cents: 3000,
price_per_month_cents: 3000,
#price_duration_30_cents: 3000,
price_duration_45_cents: 3000,
#price_duration_60_cents: 3000,
#price_duration_90_cents: 3000,
price_duration_120_cents: 3000,
#price_per_lesson_30_cents: 3000,
price_per_lesson_45_cents: 3000,
#price_per_lesson_60_cents: 3000,
#price_per_lesson_90_cents: 3000,
price_per_lesson_120_cents: 3000,
validate_pricing:true
)

View File

@ -28,7 +28,7 @@ logger = context.JK.logger
for object in this.props.sourceObjects
nm = "check_#{object.id}"
checkedStr = if @isChecked(object.id) then "checked" else ""
object_options.push `<div className='checkItem'><input type='checkbox' name={nm} data-object-id={object.id} defaultChecked={checkedStr}></input><label htmlFor={nm}>{object.description}</label></div>`
object_options.push `<div className='checkItem'><input type='checkbox' key={object.id} name={nm} data-object-id={object.id} defaultChecked={checkedStr}></input><label htmlFor={nm}>{object.description}</label></div>`
`<div className="CheckBoxList react-component">
<div className="checkbox-scroller left">

View File

@ -3,38 +3,50 @@ rest = window.JK.Rest()
logger = context.JK.logger
@TeacherExperienceEditableList = React.createClass({
#instruments: []
componentDidUnmount: () ->
#@instruments = []
@root.off("submit", ".teacher-experience-teaching-form")
componentDidMount: () ->
$root = jQuery(this.getDOMNode())
#rest.getInstruments().done (instruments) =>
#@instruments = instruments
$root.off("submit", ".teacher-experience-teaching-form").on("submit", ".teacher-experience-teaching-form", @addExperience)
@root = jQuery(this.getDOMNode())
@root.off("submit", ".teacher-experience-teaching-form").on("submit", ".teacher-experience-teaching-form", @addExperience)
formatListItem: (obj) ->
"#{obj.name}"
t = "#{obj.name}/#{obj.organization} (#{obj.start_year}"
t += "-#{obj.end_year}" if this.props.showEndDate
t += ")"
listItems: () ->
logger.debug("listItems", this.props.listItems)
this.props.listItems
getInitialProps: () ->
{listItems: []}
sortListItems: () ->
this.props.listItems ||= []
this.props.listItems = _.sortBy(this.props.listItems, 'start_year')
addError: (k,v) ->
teacherField = @root#.find(".teacher-field[name='#{k}'] > td").first()
teacherField.prepend("<div class='error-text'>#{v.join()}</div>")
$("input", teacherField).addClass("input-error")
addExperience: (e) ->
e.preventDefault()
logger.debug("addExperience", this.props.listItems, this.props)
$form = e.target
formValues = $(":input", $form).formToObject()
this.props.listItems.push {
name: $("[name='title_input']", $form).val()
organization: $("[name='organization_input']", $form).val()
start_year: $("[name='start_year']", $form).val()
end_year: $("[name='end_year']", $form).val()
}
logger.debug("addExperience", this.props.listItems)
this.props.onItemChanged(this.props.experienceType, this.props.listItems)
$form.reset()
start_year = $("[name='start_year']", $form).val()
end_year = $("[name='end_year']", $form).val()
if this.props.showEndDate && start_year > end_year
this.addError("date", ["End year must be greater than start year"])
else
this.props.listItems.push {
name: $("[name='title_input']", $form).val()
organization: $("[name='organization_input']", $form).val()
start_year: start_year
end_year: end_year
}
logger.debug("addExperience", this.props.listItems)
this.props.onItemChanged(this.props.experienceType, this.props.listItems)
$form.reset()
false
render: () ->
@ -42,23 +54,33 @@ logger = context.JK.logger
if this.props.showEndDate
endDate.push `<span><label htmlFor="end-year">to</label>
<YearSelect name="end_year"></YearSelect></span>`
dtLabel = "Start & End"
else
dtLabel = "Date"
titleLabel = this.props.titleLabel
orgLabel = this.props.orgLabel
titleLabel ||= "Title"
orgLabel ||= "School/Org"
listItems= _.sortBy(this.props.listItems, 'start_year')
`<div className="TeacherExperienceEditableList react-component">
<form className="teacher-experience-teaching-form">
<table className="form-table">
<tr className="teacher-field">
<td><label htmlFor="title-input">Title:</label></td>
<tr className="teacher-field" name="title">
<td><label htmlFor="title-input">{titleLabel}:</label></td>
<td><input name="title_input" required="required"> </input></td>
</tr>
<tr className="teacher-field">
<td><label htmlFor="organization-input">School / Org:</label></td>
<tr className="teacher-field" name="organization">
<td><label htmlFor="organization-input">{orgLabel}:</label></td>
<td><input name="organization_input" required="required"> </input></td>
</tr>
<tr className="teacher-field">
<td><label htmlFor="start-year">Start & End:</label></td>
<tr className="teacher-field" name="date">
<td><label htmlFor="start-year">{dtLabel}:</label></td>
<td>
<div className="inline-fields">
<YearSelect name="start_year"> </YearSelect>
<YearSelect name="start_year"> </YearSelect>
{endDate}
</div>
</td>
@ -66,6 +88,6 @@ logger = context.JK.logger
</table>
<button className="button-grey right" type="submit">Add</button>
</form>
<EditableList objectName='experience' onItemChanged={this.props.onItemChanged} listItems={this.props.listItems} formatListItem={this.formatListItem}/>
<EditableList objectName='experience' onItemChanged={this.props.onItemChanged} listItems={listItems} formatListItem={this.formatListItem}/>
</div>`
})

View File

@ -56,10 +56,7 @@ rest = window.JK.Rest()
navTo = this.navDestination(e)
teacherActions.change.trigger(this.state, {navTo: navTo})
render: () ->
instrumentsTaughtCheckboxes = []
#instrumentsTaughtCheckboxes.push(`<input />`)
render: () ->
# Render the following:
# Instruments
# Subjects

View File

@ -11,7 +11,11 @@ rest = window.JK.Rest()
]
getInitialState: () ->
{}
{
experiences_teaching: []
experiences_education: []
experiences_award: []
}
screenName: () ->
"experience"
@ -22,9 +26,9 @@ rest = window.JK.Rest()
teacher = changes.teacher
this.setState({
#validate_basics: true,
experience_teaching: teacher.experience_teaching
experience_education: teacher.experience_education
experience_award: teacher.experience_award
experiences_teaching: teacher.experiences_teaching
experiences_education: teacher.experiences_education
experiences_award: teacher.experiences_award
})
captureFormState: (changes) ->
@ -54,29 +58,36 @@ rest = window.JK.Rest()
handleListChange: (listName, listObjects)->
logger.debug("EXPERIENCE handleListChange:", listName, listObjects)
this.setState({
"experience_#{listName}": listObjects
"experiences_#{listName}": listObjects
})
#this.forceUpdate()
render: () ->
logger.debug("RENDERING TeacherSetupExperience", this.props, this.state)
instrumentsTaughtCheckboxes = []
#instrumentsTaughtCheckboxes.push(`<input />`)
`<div className="TeacherSetupExperience TeacherSetupComponent">
<div className="teacher-third-column">
<label className="sub-caption">TEACHING EXPERIENCE:</label>
<TeacherExperienceEditableList showEndDate="true" experienceType="teaching" onItemChanged={this.handleListChange} listItems={this.state.experience_teaching}/>
<TeacherExperienceEditableList showEndDate="true" experienceType="teaching" onItemChanged={this.handleListChange} listItems={this.state.experiences_teaching}/>
</div>
<div className="teacher-third-column">
<label className="sub-caption">EDUCATION:</label>
<TeacherExperienceEditableList showEndDate="true" experienceType="education" onItemChanged={this.handleListChange} listItems={this.state.experience_education}/>
<TeacherExperienceEditableList
showEndDate="true"
experienceType="education"
onItemChanged={this.handleListChange}
titleLabel="Degree/Cert"
orgLabel="School"
listItems={this.state.experiences_education}/>
</div>
<div className="teacher-third-column">
<label className="sub-caption">AWARDS:</label>
<TeacherExperienceEditableList experienceType="award" onItemChanged={this.handleListChange} listItems={this.state.experience_award}/>
<TeacherExperienceEditableList
experienceType="award"
onItemChanged={this.handleListChange}
titleLabel="Award"
orgLabel="Organization"
listItems={this.state.experiences_award}/>
</div>
<TeacherSetupNav handleNav={this.handleNav}> </TeacherSetupNav>

View File

@ -21,7 +21,6 @@ rest = window.JK.Rest()
logger.debug("onTeacherIntroStateChanged", changes, changes.errors?, changes.errors)
unless this.handleErrors(changes)
teacher = changes.teacher
logger.debug("@teacher", teacher)
this.setState({
biography: teacher.biography,
introductory_video: teacher.introductory_video,
@ -30,56 +29,45 @@ rest = window.JK.Rest()
validate_introduction: true
})
captureFormState: (changes) ->
$root = jQuery(this.getDOMNode())
this.setState({
biography: $root.find(".teacher-biography").val(),
introductory_video: $root.find(".teacher-introductory-video").val(),
years_teaching: $root.find(".years-teaching-experience").val(),
years_playing: $root.find(".years-playing-experience").val()
});
logger.debug("capturedFormState", this.state, changes)
handleTextChange: (e) ->
this.setState({"#{e.target.name}": e.target.value})
navDestination: (instructions) ->
navTo=null
if instructions?
logger.debug("handling instructions", instructions)
if instructions.direction=="cancel" || instructions.direction=="back"
navTo = @teacherSetupSource()
else if instructions.direction=="next"
logger.debug("redireing to basics")
navTo = @teacherSetupDestination('basics')
navTo
handleNav: (e) ->
logger.debug("handleNav #{this.screenName()}: ", this.state, this, e)
navTo = this.navDestination(e)
teacherActions.change.trigger(this.state, {navTo: navTo})
render: () ->
logger.debug("RENDERING TeacherSetupIntroduction", this.props, this.state)
`<div className="TeacherSetupIntroduction TeacherSetupComponent" >
<div className="teacher-big-column left">
<div className="teacher-field" name="biography">
<label for="teacher-biography">Teacher Bio:</label>
<textarea className="teacher-biography" rows="12" value={this.state.biography} onChange={this.captureFormState} required/>
<label htmlFor="teacher-biography">Teacher Bio:</label>
<textarea className="teacher-biography" name="biography" ref="biography" rows="12" value={this.state.biography} onChange={this.handleTextChange} required/>
</div>
</div>
<div className="teacher-small-column left">
<div className="teacher-field" name="introductory_video">
<label for="teacher-introductory-video">Teacher Introductory Video:</label>
<input className="teacher-introductory-video youtube-video" type="url" maxLength="1024" value={this.state.introductory_video} onChange={this.captureFormState} required/>
<label htmlFor="teacher-introductory-video">Teacher Introductory Video:</label>
<input className="teacher-introductory-video name="introductory_video" ref="introductory_video" youtube-video" type="url" maxLength="1024" value={this.state.introductory_video} onChange={this.handleTextChange} required/>
<em>(enter YouTube URL)</em>
</div>
<div className="teacher-field" name="years_teaching">
<label for="years-teaching-experience">Years Teaching Experience:</label>
<input className="years-teaching-experience" type="number" min="0" max="99" value={this.state.years_teaching} onChange={this.captureFormState} value="0" placeholder="Select" />
<label htmlFor="years-teaching-experience">Years Teaching Experience:</label>
<input className="years-teaching-experience" name="years_teaching_experience" ref ="years_teaching_experience" type="number" min="0" max="99" value={this.state.years_teaching} onChange={this.handleTextChange} value="0" placeholder="Select" />
</div>
<div className="teacher-field" name="years_playing">
<label for="teacher-playing-experience">Years Playing Experience:</label>
<input className="years-playing-experience" type="number" min="0" max="99" value={this.state.years_playing} onChange={this.captureFormState} value="0" placeholder="Select" />
<label htmlFor="teacher-playing-experience">Years Playing Experience:</label>
<input className="years-playing-experience" name="years_playing_experience" ref="years_playing_experience" type="number" min="0" max="99" value={this.state.years_playing} onChange={this.handleTextChange} value="0" placeholder="Select" />
</div>
</div>
<TeacherSetupNav handleNav={this.handleNav}/>

View File

@ -16,7 +16,7 @@ SessionActions = @SessionActions
render: () ->
console.log("SetupNav: this.props", this.state, this.ref, this.props.handleNav)
`<div className="TeacherSetupNav">
`<div className="TeacherSetupNav right">
<a className="nav-button button-grey" onClick={this.navBack}>
BACK
</a>

View File

@ -10,40 +10,91 @@ rest = window.JK.Rest()
Reflux.listenTo(TeacherStore, "onTeacherStateChanged")
]
componentDidUnmount: () ->
@root.off("change", ".checkbox-enabler")
componentDidMount: () ->
@root = jQuery(this.getDOMNode())
@enableCheckBoxTargets()
getInitialState: () ->
{}
enableCheckBoxTargets: (e) ->
# Do unchecked boxes last since any unchecked target has priority:
checkBoxes = $("input[type='checkbox'].checkbox-enabler:checked", @root)
checkBoxes = $.merge(checkBoxes, $("input[type='checkbox'].checkbox-enabler:not(:checked)", @root))
checkBoxes.each ->
targetClass = $(this).data("enable-target")
if ($(this).is(":checked"))
$(".#{targetClass}").removeClass("invisible")
else
$(".#{targetClass}").addClass("invisible")
true
screenName: () ->
"pricing"
onTeacherStateChanged: (changes) ->
$root = jQuery(this.getDOMNode())
logger.debug("onTeacherPricingStateChanged", changes, changes.errors?, changes.errors)
unless this.handleErrors(changes)
teacher = changes.teacher
logger.debug("@teacher", teacher)
this.setState({
biography: teacher.biography,
introductory_video: teacher.introductory_video,
years_teaching: teacher.years_teaching,
years_playing: teacher.years_playing,
validate_pricing: true
price_per_lesson_30_cents: teacher.price_per_lesson_30_cents
price_per_lesson_45_cents: teacher.price_per_lesson_45_cents
price_per_lesson_60_cents: teacher.price_per_lesson_60_cents
price_per_lesson_90_cents: teacher.price_per_lesson_90_cents
price_per_lesson_120_cents: teacher.price_per_lesson_120_cents
price_per_month_30_cents: teacher.price_per_month_30_cents
price_per_month_45_cents: teacher.price_per_month_45_cents
price_per_month_60_cents: teacher.price_per_month_60_cents
price_per_month_90_cents: teacher.price_per_month_90_cents
price_per_month_120_cents: teacher.price_per_month_120_cents
prices_per_lesson: teacher.prices_per_lesson
prices_per_month: teacher.prices_per_month
lesson_duration_30: teacher.lesson_duration_30
lesson_duration_45: teacher.lesson_duration_45
lesson_duration_60: teacher.lesson_duration_60
lesson_duration_90: teacher.lesson_duration_90
lesson_duration_120: teacher.lesson_duration_120
})
@enableCheckBoxTargets()
false
captureFormState: (e) ->
this.setState({
prices_per_lesson: $("[name='prices_per_lesson_input']", @root).is(":checked")
prices_per_month: $("[name='prices_per_month_input']", @root).is(":checked")
lesson_duration_30: $("[name='lesson_duration_30_input']", @root).is(":checked")
lesson_duration_45: $("[name='lesson_duration_45_input']", @root).is(":checked")
lesson_duration_60: $("[name='lesson_duration_60_input']", @root).is(":checked")
lesson_duration_90: $("[name='lesson_duration_90_input']", @root).is(":checked")
lesson_duration_120: $("[name='lesson_duration_120_input']", @root).is(":checked")
})
@enableCheckBoxTargets()
#this.forceUpdate()
captureCurrency: (e) ->
for minutes in [30, 45, 60, 90, 120]
pricePerLessonCents = context.JK.ProfileUtils.normalizeMoneyForSubmit($("[name='price_per_lesson_#{minutes}_cents']", @root).val())
pricePerMonthCents = context.JK.ProfileUtils.normalizeMoneyForSubmit($("[name='price_per_month_#{minutes}_cents']", @root).val())
this.setState({
"price_per_lesson_#{minutes}_cents": pricePerLessonCents
"price_per_month_#{minutes}_cents": pricePerMonthCents
})
captureFormState: (changes) ->
$root = jQuery(this.getDOMNode())
this.setState({
biography: $root.find(".teacher-biography").val(),
introductory_video: $root.find(".teacher-introductory-video").val(),
years_teaching: $root.find(".years-teaching-experience").val(),
years_playing: $root.find(".years-playing-experience").val()
});
logger.debug("capturedFormState", this.state, changes)
displayLessonAmount = context.JK.ProfileUtils.normalizeMoneyForDisplay(pricePerLessonCents)
displayMonthAmount = context.JK.ProfileUtils.normalizeMoneyForDisplay(pricePerMonthCents)
$("[name='price_per_lesson_#{minutes}_cents']", @root).val(displayLessonAmount)
$("[name='price_per_month_#{minutes}_cents']", @root).val(displayMonthAmount)
navDestination: (instructions) ->
navTo=null
if instructions?
logger.debug("handling instructions", instructions)
if instructions.direction=="cancel"
navTo = @teacherSetupSource()
else if instructions.direction=="back"
@ -55,24 +106,91 @@ rest = window.JK.Rest()
navTo
handleNav: (e) ->
logger.debug("handleNav #{this.screenName()}: ", this.state, this, e)
navTo = this.navDestination(e)
teacherActions.change.trigger(this.state, {navTo: navTo})
render: () ->
logger.debug("RENDERING TeacherSetupPricing", this.props, this.state)
instrumentsTaughtCheckboxes = []
#instrumentsTaughtCheckboxes.push(`<input />`)
handleTextChange: (e) ->
this.setState({"#{e.target.name}": e.target.value})
`<div className="TeacherSetupPricing TeacherSetupComponent" >
<div className="teacher-quarter-column left">
<div className="teacher-field" name="instruments_taught">
<label for="teacher-instruments-taught">Instruments Taught:</label>
<div className="checkbox-scroller">
handleCheckChange: (e) ->
@enableCheckBoxTargets()
this.setState({"#{e.target.name}": e.target.value=="on"})
render: () ->
priceRows = []
for minutes in [30, 45, 60, 90, 120]
pricePerLessonCents = context.JK.ProfileUtils.normalizeMoneyForDisplay(this.state["price_per_lesson_"+minutes+"_cents"])
pricePerMonthCents = context.JK.ProfileUtils.normalizeMoneyForDisplay(this.state["price_per_month_"+minutes+"_cents"])
pricePerLessonName = "price_per_lesson_#{minutes}_cents"
pricePerMonthName = "price_per_month_#{minutes}_cents"
inputName = "lesson_duration_"+minutes+"_input"
priceRows.push `
<div className="teacher-price-row" key={minutes}>
<div className="teacher-half-column left pricing-options">
<div className="teacher-field" name={"lesson_duration_"+minutes}>
<input type='checkbox' className="checkbox-enabler" data-enable-target={"lesson-"+minutes+"-target"} name={inputName} key={inputName} checked={this.state["lesson_duration_"+minutes]} onChange={this.handleCheckChange} ref={inputName}/>
<label htmlFor={inputName} key={minutes} className="checkbox-label">{minutes} Minutes</label>
</div>
</div>
<div className="teacher-half-column right pricing-amounts">
<div className={"teacher-field lesson-"+minutes+"-target"}>
<div className="teacher-third-column inline">
<label>{minutes} Minutes</label>
</div>
<div className="teacher-third-column inline per-lesson">
<input key={minutes} name={pricePerLessonName} ref={pricePerLessonName} className="per-lesson-target" type="text" min="0" max="100000" value={pricePerLessonCents} onBlur={this.captureCurrency} onChange={this.handleTextChange} />
</div>
<div className="teacher-third-column inline per-month">
<input key={minutes} name={pricePerMonthName} ref={pricePerMonthName} className="per-month-target" type="text" min="0" max="100000" value={pricePerMonthCents} onBlur={this.captureCurrency} onChange={this.handleTextChange} />
</div>
</div>
</div>
<br className="clearall"/>
</div>`
# Render:
`<div className="TeacherSetupPricing TeacherSetupComponent" >
<div className="teacher-half-column left pricing-options">
<label className="margined">Offer Lessons Pricing & Payments:</label>
<div className="teacher-field" name="prices_per_lesson">
<input type='checkbox' className='checkbox-enabler' data-enable-target="per-lesson-target" name="prices_per_lesson_input" checked={this.state.prices_per_lesson} ref="prices_per_lesson" onChange={this.handleCheckChange}></input>
<label htmlFor='prices_per_lesson_input' className="checkbox-label">Per Lesson</label>
</div>
<div className="teacher-field" name="prices_per_month">
<input type='checkbox' className='checkbox-enabler' data-enable-target="per-month-target" name="prices_per_month_input" checked={this.state.prices_per_month} ref="prices_per_month" onChange={this.handleCheckChange}></input>
<label htmlFor='prices_per_month_input' className="checkbox-label">Per Month</label>
</div>
</div>
<TeacherSetupNav handleNav={this.handleNav}/>
<div className="teacher-half-column right pricing-options">
<label className="margined">Please fill in the prices (in US Dollars) for the lessons you have chosen to offer in the boxes below:</label>
</div>
<br className="clearall"/>
<div className="teacher-price-row">
<div className="teacher-half-column left pricing-options">
<label className="margined">Offer Lessons of These Durations:</label>
</div>
<div className="teacher-half-column right pricing-options">
<div className="teacher-third-column">&nbsp;</div>
<div className="teacher-third-column">
<label className="margined">Price Per Lesson</label>
</div>
<div className="teacher-third-column">
<label className="margined">Price Per Month</label>
</div>
</div>
<br className="clearall"/>
</div>
{priceRows}
<br className="clearall"/>
<TeacherSetupNav handleNav={this.handleNav}></TeacherSetupNav>
</div>`

View File

@ -18,7 +18,7 @@ logger = context.JK.logger
for yr in [1901..now]
options.push `<option value={yr}>{yr}</option>`
`<select className="YearSelect react-component" required placeholder="Select">
`<select className="YearSelect react-component" name={this.props.name} required placeholder="Select">
{options}
</select>`
})

View File

@ -7,7 +7,7 @@ teacherActions = window.JK.Actions.Teacher
screenBindings = {
'beforeShow': @beforeShow
}
@root = jQuery(this.getDOMNode())
@app.bindScreen("teachers/setup/#{@screenName()}", screenBindings)
beforeShow: (data) ->
@ -26,16 +26,17 @@ teacherActions = window.JK.Actions.Teacher
# @postmark = params.p
handleErrors: (changes) ->
$root = jQuery(this.getDOMNode())
$(".error-text", $root).remove()
$(".error-text", @root).remove()
if changes.errors?
for k,v of changes.errors
teacherField = $root.find(".teacher-field[name='#{k}']")
teacherField.append("<div class='error-text'>#{v.join()}</div>")
$("input", teacherField).addClass("input-error")
addError(k,v) for k,v of changes.errors
changes.errors?
addError: (k,v) ->
teacherField = @root.find(".teacher-field[name='#{k}']")
teacherField.append("<div class='error-text'>#{v.join()}</div>")
$("input", teacherField).addClass("input-error")
getParams:() =>
params = {}
q = window.location.href.split("?")[1]

View File

@ -18,6 +18,13 @@ EVENTS = context.JK.EVENTS
onAppInit: (app) ->
@app = app
defaultTeacher: ->
{
experiences_teaching: []
experiences_education: []
experiences_award: []
}
onLoadTeacher: (options) ->
logger.debug("onLoadTeacher", options)
if !options?
@ -30,7 +37,7 @@ EVENTS = context.JK.EVENTS
.fail((jqXHR, textStatus, errorMessage) =>
logger.debug("FAILED",jqXHR, textStatus, errorMessage)
if (jqXHR.status==404)
this.trigger({teacher: {}})
this.trigger({teacher: this.defaultTeacher()})
else
context.JK.app.ajaxError(jqXHR, textStatus, errorMessage)
)

View File

@ -1,6 +1,10 @@
@import "client/common.css.scss";
@import "client/screen_common.css.scss";
.invisible {
visibility: hidden;
}
.react-component {
width: 100%;
em {
@ -10,16 +14,13 @@
.checkbox-scroller, .editable-scroller {
overflow-x: hidden;
overflow-y: scroll;
//position: relative;
width: 100%;
// min-height: 10em;
// max-height: 20em;
min-height: 5em;
max-height: 20em;
@include border_box_sizing;
text-align:left;
//border-right: 1px solid #4c4c4c;
margin-bottom:2em;
margin-top:2em;
margin-bottom:0.5em;
margin-top:0.5em;
padding: 0.25em;
}

View File

@ -4,24 +4,32 @@
.teacher-setup {
font-family: Raleway, Arial, Helvetica, verdana, arial, sans-serif;
h2 {
margin-bottom: 0.75em;
}
.TeacherSetupComponent {
@include border_box_sizing;
//float: left;
position: relative;
//clear: both;
height: 100%;
.TeacherSetupNav {
clear: both;
margin: 1em 0em 1em 0em;
@include border_box_sizing;
float: left;
// margin-top:90%;
// position: absolute;
// bottom: 40px;
// right: 40px;
float: right;
}
}
label.margined {
margin-top: 1em;
margin-bottom: 0.25em;
}
.inline {
display: inline;
//width: auto !important;
}
.teacher-setup-form {
padding: 1em;
.error-text {
@ -41,8 +49,13 @@
@extend .w25;
}
.teacher-half-column {
@extend .w50;
}
.teacher-third-column {
@include border_box_sizing;
min-width: 1px;
width: 33%;
float: left;
padding-right: 1em;
@ -67,8 +80,9 @@
}
.teacher-field {
padding: 1em;
padding: 0.25em;
@include border_box_sizing;
input, select, textarea {
@include border_box_sizing;
@ -79,19 +93,6 @@
overflow: hidden;
}
em {
font-style: italic;
}
label {
margin-bottom: 4px;
}
textarea {
height: auto;
overflow:hidden;
}
input[type="number"] {
border-radius: 4px;
@ -100,6 +101,32 @@
height: 24px;
//padding: 5px;
}
input[type="checkbox"] {
display: inline;
width: auto;
vertical-align: center;
padding: 4px;
}
em {
font-style: italic;
}
label {
padding: 4px;
}
label.checkbox-label {
display: inline;
}
textarea {
height: auto;
overflow:hidden;
}
}

View File

@ -10,13 +10,16 @@ attributes :id,
:lesson_duration_45,
:lesson_duration_60,
:lesson_duration_90,
:price_duration_120_cents,
:price_duration_30_cents,
:price_duration_45_cents,
:price_duration_60_cents,
:price_duration_90_cents,
:price_per_lesson_cents,
:price_per_month_cents,
:price_per_lesson_120_cents,
:price_per_lesson_30_cents,
:price_per_lesson_45_cents,
:price_per_lesson_60_cents,
:price_per_lesson_90_cents,
:price_per_month_120_cents,
:price_per_month_30_cents,
:price_per_month_45_cents,
:price_per_month_60_cents,
:price_per_month_90_cents,
:prices_per_lesson,
:prices_per_month,
:teaches_advanced,
@ -48,7 +51,7 @@ node :languages do
@teacher.languages.collect{|o|o.id}
end
node :experience_teaching do
node :experiences_teaching do
@teacher.experiences_teaching.collect do |o|
{
name: o.name,
@ -60,7 +63,7 @@ node :experience_teaching do
end # collect
end
node :experience_education do
node :experiences_education do
@teacher.experiences_education.collect do |o|
{
name: o.name,
@ -72,7 +75,7 @@ node :experience_education do
end # collect
end
node :experience_award do
node :experiences_award do
@teacher.experiences_award.collect do |o|
{
name: o.name,

View File

@ -1,4 +1,4 @@
#teacher-setup-pricing.screen.secondary layout="screen" layout-id="teachers/setup/pricing"
#teacher-setup-pricing.teacher-setup.screen.secondary layout="screen" layout-id="teachers/setup/pricing"
.content-head
.content-icon
= image_tag "content/icon_bands.png", :size => "19x19"

View File

@ -225,10 +225,9 @@ describe ApiTeachersController do
lesson_duration_60: false,
lesson_duration_90: false,
lesson_duration_120: false,
price_per_lesson_cents: 3000,
price_per_month_cents: 3000,
price_duration_45_cents: 3000,
price_duration_120_cents: 3000,
price_per_lesson_45_cents: 3000,
price_per_lesson_120_cents: 3000,
price_per_month_30_cents: 5000,
validate_pricing: true,
format: 'json'
}