Created
June 8, 2020 20:46
-
-
Save trdev7/5dc75776e7701ef6709934ef460f6169 to your computer and use it in GitHub Desktop.
ProductForm on Bookthatapp
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
import React, { Component } from 'react'; | |
import FormSelect from '../common/form_select.js.jsx'; | |
import FormInput from '../common/form_input.js.jsx'; | |
import FormNumberInput from '../common/form_number_input.js.jsx'; | |
import FormInputWithTime from './form_input_with_time.js.jsx'; | |
import FormNested from './form_nested.js.jsx'; | |
import LocationInventories from './location_inventories.jsx'; | |
import ProductVariantsForm from './product_variants_form.js.jsx'; | |
import RoomTime from './room_time.js.jsx'; | |
import FormCheckbox from '../common/form_checkbox.js.jsx'; | |
import ProductCapacitiesForm from './product_capacities_form.js.jsx'; | |
import ProductDurationsForm from './product_durations_form.js.jsx'; | |
import ScheduleForm from '../common/schedule_form.js.jsx'; | |
import WidgetPicker from '../common/widget_picker.js.jsx'; | |
import Tooltip from '../common/tooltip.js.jsx'; | |
import ProductTerms from './product_terms.js.jsx'; | |
import ErrorsContainer from './errors_container.js.jsx'; | |
import InputFeeds from './input_feeds.js.jsx'; | |
import Tabs from '../common/tabs'; | |
import Tab from '../common/tab'; | |
import FormDeleteButtonContainer from '../../containers/form_delete_button'; | |
import Utils from '../common/utils.js'; | |
import CopyProductForm from './copy_product_form.js'; | |
import { | |
PRODUCT_PROFILE, | |
ACTIVITY_PROFILE, | |
COURSE_PROFILE, | |
CLASS_PROFILE, | |
APPT_PROFILE, | |
ROOM_PROFILE, | |
GENERAL_PROFILE, | |
} from '../../reducers/product.js.jsx'; | |
import ActionsList from '../common/actions_list.js'; | |
import ProductResources from './product_resources.jsx'; | |
require('react-datetime/css/react-datetime.css'); | |
class ProductForm extends React.Component { | |
constructor(props) { | |
super(props); | |
this.handleResyncClick = this.handleResyncClick.bind(this); | |
} | |
_validProduct() { | |
const errors = []; | |
const inputFeedErrors = {}; | |
const self = this; | |
if (this.props.product.profile == ROOM_PROFILE) { | |
if (!this.props.product.check_in_time) { | |
errors.push('Check In Time is blank. Please set it.'); | |
} | |
if (!this.props.product.check_out_time) { | |
errors.push('Check Out Time is blank. Please set it.'); | |
} | |
} | |
this.props.product.input_feeds.forEach((feed, index) => { | |
self._validateInputFeed(feed, index, inputFeedErrors); | |
}); | |
if (Object.keys(inputFeedErrors).length > 0) { | |
errors.push('Invalid external calendar url.'); | |
} | |
this.props.actions.onSetInputFeedsValidationErrors(inputFeedErrors); | |
this.props.actions.onProductErrorsSet(errors); | |
if (errors.length > 0) { | |
Utils.onErrorShow(errors); | |
return false; | |
} | |
return true; | |
} | |
_validateInputFeed(feed, index, errors) { | |
if (feed) { | |
if (feed._destroy) { | |
return errors; | |
} | |
if (!feed.name) { | |
this._setInputFeedError(index, 'name', "can't be blank", errors); | |
} | |
if (!feed.url) { | |
this._setInputFeedError(index, 'url', "can't be blank", errors); | |
} | |
if (feed.url && !Utils.validURL(feed.url)) { | |
this._setInputFeedError(index, 'url', "is not a valid URL", errors); | |
} | |
return errors; | |
} | |
} | |
_setInputFeedError(index, field, message, errors) { | |
if (!errors[index]) { | |
errors[index] = {}; | |
} | |
errors[index][field] = message; | |
return errors; | |
} | |
onSubmit() { | |
if (this._validProduct()) { | |
this.props.actions.onProductSubmit(this.productData()); | |
} | |
} | |
productData() { | |
// build request data with input_feeds_attributes | |
let feeds = this.props.product.input_feeds.filter(feed => feed); | |
let data = Object.assign({input_feeds_attributes: feeds}, this.props.product); | |
delete data.input_feeds; | |
return data | |
} | |
durationEnabled() { | |
return this.props.product.scheduled || this.props.duration_enabled; | |
} | |
getScheduleDuration() { | |
let duration = this.props.product.duration || 3600; | |
if (this.props.product.range_count_basis == '0' && duration > 86400) { | |
duration += 86400; | |
} | |
return duration; | |
} | |
goToPage(url) { | |
if (this.props.product.id) { | |
if (this.props.isModified) { | |
const r = confirm( | |
'If you leave this page, all unsaved changes will be lost. Are you sure you want to leave this page?', | |
); | |
if (r == true) { | |
window.location.href = url; | |
} | |
} else { | |
window.location.href = url; | |
} | |
} else alert('This product should be saved first'); | |
} | |
handleResyncClick(e) { | |
e.preventDefault(); | |
var self = this; | |
btaa.notify('Synchronization in progress...'); | |
self.props.actions.onProductLoadingSet(true); | |
fetch(`/admin/products/${this.props.product.id}/resync`, { | |
method: 'post', | |
headers: { | |
'Content-Type': 'application/json' | |
}, | |
credentials: 'same-origin' | |
}).then(function (response) { | |
return response.json(); | |
}).then(function(data) { | |
btaa.notify(data.message); | |
self.props.actions.onProductLoadingSet(false); | |
window.location = window.location; // reload | |
}); | |
} | |
notificationActions() { | |
if (Utils.gup('created')) { | |
const actions = [ | |
{ title: 'Create Another Product', url: '/admin/products/new' }, | |
{ | |
title: 'View Product', | |
url: `https:${this.props.product.shopify_url}`, | |
target: '_blank', | |
icon: 'fa-desktop', | |
}, | |
{ | |
title: 'Create Product Booking', | |
url: `/admin/bookings/new?product=${this.props.product.id}`, | |
icon: 'fa-calendar-plus-o', | |
}, | |
]; | |
return actions; | |
} | |
return []; | |
} | |
isCapacityResourceBased() { | |
return this.props.product.capacity_type === 2; | |
} | |
render() { | |
const { product, multiLocationInventoryEnabled } = this.props; | |
return ( | |
<div className="form product-container"> | |
<ActionsList title="Product" itemActions={this.notificationActions()} /> | |
<ErrorsContainer message="Could not save product" errors={this.props.productErrors} /> | |
<div className="row"> | |
<div className="col-xs-12 col-md-12"> | |
<div className="ibox"> | |
<div className="ibox-content"> | |
<Tabs className="icon-tabs"> | |
<Tab title="Product"> | |
<div id="product"> | |
<div className="row"> | |
<div className="col-sm-12"> | |
<div className="help-icon"> | |
<a className="btn" role="button" data-toggle="collapse" href="#product-help" title="Help"> | |
<i className="fa fa-info-circle" /> | |
</a> | |
</div> | |
<h3 className="m-t-none m-b">Product</h3> | |
<div id="product-help" className="panel panel-info collapse" aria-expanded="false"> | |
<div className="panel-heading"> | |
<i className="fa fa-info-circle m-r-xs" /> | |
Help | |
</div> | |
<div className="panel-body"> | |
<p> | |
The product profile determines what types of bookings this product is for. Depending on what | |
profile is chosen you will see different fields. Booking forms also depend on the profile. For | |
more information please see our help article " | |
<a href="//support.zetya.com/hc/en-us/articles/211514406-How-do-I-choose-the-right-booking-form-profile-"> | |
How do I choose the right booking form/profile | |
</a> | |
". | |
</p> | |
</div> | |
</div> | |
</div> | |
<div className="col-sm-6"> | |
<div className="form-group"> | |
<label>Profile</label> | |
<FormSelect | |
name="profile" | |
value={this.props.product.profile} | |
options={this.props.profileOptions} | |
onChange={val => this.props.actions.onProfileSet(val)} | |
/> | |
</div> | |
</div> | |
<div className="col-sm-6"> | |
<div className="form-group" id="product_title"> | |
<label>Product Title</label> | |
<FormInput | |
type="text" | |
name="product_title" | |
value={this.props.product.product_title} | |
onChange={this.props.actions.onTitleSet} | |
/> | |
</div> | |
</div> | |
<div className="col-sm-6"> | |
<div className="form-group"> | |
<label>Product Handle</label> | |
<FormInput | |
type="text" | |
name="product_handle" | |
isDisabled | |
onChange={function () {}} | |
value={this.props.product.product_handle} | |
/> | |
</div> | |
</div> | |
<div className="col-sm-6"> | |
<div className="form-group"> | |
<label>Tags</label> | |
<FormInput type="text" name="tags" isDisabled value={this.props.product.tag_list.join(', ')} /> | |
</div> | |
</div> | |
{Utils.displayForProfiles([GENERAL_PROFILE], product.profile) && ( | |
<div className="col-sm-6"> | |
<div className="form-group m-t"> | |
<label className="m-r"> | |
Scheduled? | |
<Tooltip title="Select if this product is a one-off or recurring event (e.g. class) that should appear in your store's calendar." /> | |
</label> | |
<FormCheckbox | |
name="scheduled" | |
value={this.props.product.scheduled} | |
onChange={this.props.actions.onScheduledSet} | |
/> | |
</div> | |
</div> | |
)} | |
</div> | |
<div className="hr-line-dashed m-t-sm m-b-sm" /> | |
{/* === CAPACITY SECTION === */} | |
{multiLocationInventoryEnabled ? ( | |
<LocationInventories /> | |
) : ( | |
<div> | |
<div className="row"> | |
<div className="col-sm-12"> | |
<div className="help-icon"> | |
<a className="btn" role="button" data-toggle="collapse" href="#capacity-help" title="Help"> | |
<i className="fa fa-info-circle" /> | |
</a> | |
</div> | |
<h3 className="m-t-none m-b">Capacity</h3> | |
<div id="capacity-help" className="panel panel-info collapse" aria-expanded="false"> | |
<div className="panel-heading"> | |
<i className="fa fa-info-circle m-r-xs" /> | |
Help | |
</div> | |
<div className="panel-body"> | |
<p> | |
Capacity limits how many bookings can be made at any one time. Product based capacity | |
counts all bookings regardless of the variant booked. Variant based capacity counts only | |
bookings for a given variant. Resource based capacity limits the bookings to the | |
availability of the resources assigned to the product. | |
</p> | |
<p> | |
For more information, refer to our | |
{' '} | |
<a | |
href="//support.zetya.com/hc/en-us/articles/211514426-Setting-Capacity" | |
target="_blank" | |
> | |
help desk article | |
</a> | |
. | |
</p> | |
</div> | |
</div> | |
</div> | |
<div id="capacity"> | |
<ProductCapacitiesForm | |
profile={this.props.product.profile} | |
capacities={this.props.product.option_capacities_attributes} | |
capacity={this.props.product.capacity} | |
capacity_option1={this.props.product.capacity_option1} | |
capacity_option2={this.props.product.capacity_option2} | |
capacity_option3={this.props.product.capacity_option3} | |
configurationOptions={this.props.configurationOptions} | |
capacityType={this.props.product.capacity_type} | |
onCapacityOptionEnable={this.props.actions.onCapacityOptionEnable} | |
onCapacitySet={this.props.actions.onCapacitySet} | |
onCapacityTypeSet={this.props.actions.onCapacityTypeSet} | |
onOptionCapacitiesSet={this.props.actions.onOptionCapacitiesSet} | |
onOptionCapacitySet={this.props.actions.onOptionCapacitySet} | |
variantOptions={this.props.variantOptions} | |
capacitiesOptions={this.props.capacityOptions} | |
/> | |
</div> | |
</div> | |
<div className="hr-line-dashed m-t-sm m-b-sm" /> | |
</div> | |
)} | |
{/* === DURATION SECTION === */} | |
{Utils.displayForProfiles( | |
[PRODUCT_PROFILE, ACTIVITY_PROFILE, CLASS_PROFILE, APPT_PROFILE, GENERAL_PROFILE, COURSE_PROFILE], | |
product.profile, | |
) | |
&& this.durationEnabled() && ( | |
<div> | |
<div className="row"> | |
<div className="col-sm-12"> | |
<div className="help-icon"> | |
<a className="btn" role="button" data-toggle="collapse" href="#duration-help" title="Help"> | |
<i className="fa fa-info-circle" /> | |
</a> | |
</div> | |
<h3 className="m-t-none m-b">Duration</h3> | |
<div id="duration-help" className="panel panel-info collapse" aria-expanded="false"> | |
<div className="panel-heading"> | |
<i className="fa fa-info-circle m-r-xs" /> | |
Help | |
</div> | |
<div className="panel-body"> | |
<p> | |
Determines how the end date for bookings is calculated. If the duration is the same | |
across all variants set the Duration Basis to Product. If the duration changes depending | |
on what variant is selected, or if you are using a date range and want to change the | |
rate depending on how many days are selected, choose Variant Option. | |
</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div className="row"> | |
<div id="duration"> | |
<ProductDurationsForm | |
profile={this.props.product.profile} | |
duration={this.props.product.duration || 3600} | |
allDay={this.props.product.all_day} | |
durationOption={this.props.product.duration_option} | |
durationOptionRangeVariant={this.props.product.duration_option_range_variant} | |
durationOptions={this.props.durationOptions} | |
onDurationSet={this.props.actions.onDurationSet} | |
onProductAllDaySet={this.props.actions.onProductAllDaySet} | |
onDurationTypeSet={this.props.actions.onDurationTypeSet} | |
onDurationOptionRangeVariantSet={this.props.actions.onDurationOptionRangeVariantSet} | |
onDurationOptionSet={this.props.actions.onDurationOptionSet} | |
activityDurationVariantsEnabled={this.props.activity_duration_variants_enabled} | |
durationEnabled={this.props.duration_enabled} | |
durationType={this.props.product.duration_type} | |
variantOptions={this.props.variantOptions} | |
optionDurations={this.props.product.option_durations_attributes} | |
onOptionDurationSet={this.props.actions.onOptionDurationSet} | |
onOptionDurationsSet={this.props.actions.onOptionDurationsSet} | |
/> | |
</div> | |
</div> | |
<div className="hr-line-dashed m-t-sm m-b-sm" /> | |
</div> | |
)} | |
{/* === ROOM SECTION === */} | |
{Utils.displayForProfiles([ROOM_PROFILE], product.profile) && ( | |
<RoomTime | |
checkIn={this.props.product.check_in_time} | |
checkOut={this.props.product.check_out_time} | |
changeCheckInTime={val => this.props.actions.onCheckInTimeSet(val)} | |
changeCheckOutTime={val => this.props.actions.onCheckOutTimeSet(val)} | |
/> | |
)} | |
{/* === LEAD/LAG TIME & DATEPICKER SECTIONS === */} | |
{Utils.displayForProfiles( | |
[PRODUCT_PROFILE, ACTIVITY_PROFILE, APPT_PROFILE, ROOM_PROFILE, GENERAL_PROFILE, CLASS_PROFILE], | |
product.profile, | |
) && ( | |
<div className="row"> | |
{Utils.displayForProfiles( | |
[PRODUCT_PROFILE, ACTIVITY_PROFILE, APPT_PROFILE, GENERAL_PROFILE], | |
product.profile, | |
) && ( | |
<div className="col-md-6"> | |
<div className="help-icon"> | |
<a className="btn" role="button" data-toggle="collapse" href="#leadlag-help" title="Help"> | |
<i className="fa fa-info-circle" /> | |
</a> | |
</div> | |
<h3 className="m-t-none m-b">Lead/Lag Times</h3> | |
<div id="leadlag-help" className="panel panel-info collapse" aria-expanded="false"> | |
<div className="panel-heading"> | |
<i className="fa fa-info-circle m-r-xs" /> | |
Help | |
</div> | |
<div className="panel-body"> | |
<p> | |
<strong>Lead Time</strong> | |
: Number of days to allow before the next booking can begin, for | |
example to account for delivery time. Unlike lag time, the lead time component is not | |
included when a booking is created from an order. | |
</p> | |
<p> | |
<strong>Lag Time</strong> | |
: Lag time is automatically added to the booking finish time on | |
creation. Typically this is used to account for the time required to get an item ready | |
before the next booking. | |
</p> | |
</div> | |
</div> | |
<div className="row"> | |
<div className="col-sm-6"> | |
<div className="form-group"> | |
<label htmlFor="lead_time">Lead Time (Days)</label> | |
<FormNumberInput | |
id="lead_time" | |
name="lead_time" | |
value={this.props.product.lead_time} | |
min={0} | |
onChange={this.props.actions.onLeadTimeSet} | |
/> | |
</div> | |
</div> | |
<div className="col-sm-6"> | |
<div className="form-group" id="lag_time"> | |
<label htmlFor="lag_time">Lag Time</label> | |
<FormInputWithTime | |
id="lag_time" | |
name="lag_time" | |
value={this.props.product.lag_time} | |
onChange={this.props.actions.onLagTimeSet} | |
/> | |
</div> | |
</div> | |
</div> | |
</div> | |
)} | |
{Utils.displayForProfiles( | |
[PRODUCT_PROFILE, ROOM_PROFILE, GENERAL_PROFILE, CLASS_PROFILE, ACTIVITY_PROFILE], | |
product.profile, | |
) && ( | |
<div className="col-md-6"> | |
<div className="help-icon"> | |
<a className="btn" role="button" data-toggle="collapse" href="#range-help" title="Help"> | |
<i className="fa fa-info-circle" /> | |
</a> | |
</div> | |
<h3 className="m-t-none m-b">Date Range</h3> | |
<div id="range-help" className="panel panel-info collapse" aria-expanded="false"> | |
<div className="panel-heading"> | |
<i className="fa fa-info-circle m-r-xs" /> | |
Help | |
</div> | |
<div className="panel-body"> | |
<p>Specify the minimun and maximun number for the date range.</p> | |
{Utils.displayForProfiles([PRODUCT_PROFILE, GENERAL_PROFILE], product.profile) && ( | |
<p> | |
Count is used to specify if days or nights are used when calculating quantity for the | |
'Date Range Updates Quantity Setting'. | |
</p> | |
)} | |
</div> | |
</div> | |
{Utils.displayForProfiles( | |
[PRODUCT_PROFILE, ROOM_PROFILE, GENERAL_PROFILE], | |
product.profile, | |
) && ( | |
<div className="row"> | |
<div className="col-sm-6"> | |
<div className="form-group col-md-3 no-padding"> | |
<label htmlFor="min_duration">Minimum</label> | |
<FormNumberInput | |
id="min_duration" | |
name="min_duration" | |
value={this.props.product.min_duration} | |
onChange={this.props.actions.onMinDurationSet} | |
/> | |
</div> | |
</div> | |
<div className="col-sm-6"> | |
<div className="form-group col-md-3 no-padding"> | |
<label htmlFor="max_duration">Maximum</label> | |
<FormNumberInput | |
id="max_duration" | |
name="max_duration" | |
value={this.props.product.max_duration} | |
onChange={this.props.actions.onMaxDurationSet} | |
/> | |
</div> | |
</div> | |
</div> | |
)} | |
{Utils.displayForProfiles( | |
[PRODUCT_PROFILE, GENERAL_PROFILE, CLASS_PROFILE, ACTIVITY_PROFILE], | |
product.profile, | |
) && ( | |
<div className="row"> | |
<div className="col-sm-6"> | |
<div className="form-group no-padding"> | |
<label>Count</label> | |
<FormSelect | |
value={this.props.product.range_count_basis} | |
options={this.props.rangeCountBasisOptions} | |
onChange={this.props.actions.onRangeCountBasisSet} | |
/> | |
</div> | |
</div> | |
</div> | |
)} | |
</div> | |
)} | |
<div className="col-sm-12"> | |
<div className="hr-line-dashed m-t-sm m-b-sm" /> | |
</div> | |
</div> | |
)} | |
{/* === VARIANTS SECTION === */} | |
{Utils.hideForProfiles([COURSE_PROFILE], product.profile) && ( | |
<div> | |
<div className="row"> | |
<div className="col-sm-12"> | |
<div className="help-icon"> | |
<a className="btn" role="button" data-toggle="collapse" href="#variants-help" title="Help"> | |
<i className="fa fa-info-circle" /> | |
</a> | |
</div> | |
<h3 className="m-t-none m-b">Variants</h3> | |
<div id="variants-help" className="panel panel-info collapse" aria-expanded="false"> | |
<div className="panel-heading"> | |
<i className="fa fa-info-circle m-r-xs" /> | |
Help | |
</div> | |
<div className="panel-body"> | |
<p> | |
Lists the product variants. Selecting the Hide checkbox will hide the booking form when | |
the variant is chosen on the product page. | |
</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div className="row"> | |
<div className="col-sm-12"> | |
<ProductVariantsForm | |
profile={this.props.product.profile} | |
variants={this.props.product.variants_attributes} | |
isNew={!this.props.product.id} | |
legacyVariantTimes={this.props.legacy_variant_times} | |
onVariantsSet={this.props.actions.onVariantsSet} | |
// durationEnabled={this.props.duration_enabled} | |
durationEnabled={!this.durationEnabled()} | |
scheduled={this.props.product.scheduled} | |
durationUnitsOptions={this.props.durationUnitOptions} | |
/> | |
</div> | |
</div> | |
<div className="hr-line-dashed m-t-sm m-b-sm" /> | |
</div> | |
)} | |
{/* === RESOURCES & LOCATIONS SECTION === */} | |
<div className="row"> | |
<div className="col-sm-6"> | |
<h3 className="m-t-none m-b dib">Resources</h3> | |
<div className="help-icon visible-xs"> | |
<a className="btn" role="button" data-toggle="collapse" href="#locations-help" title="Help"> | |
<i className="fa fa-info-circle" /> | |
</a> | |
</div> | |
</div> | |
<div className="col-sm-6 hidden-xs"> | |
<h3 className="m-t-none m-b dib">Locations</h3> | |
<div className="help-icon"> | |
<a className="btn" role="button" data-toggle="collapse" href="#locations-help" title="Help"> | |
<i className="fa fa-info-circle" /> | |
</a> | |
</div> | |
</div> | |
</div> | |
<div className="row"> | |
<div className="col-sm-12"> | |
<div id="locations-help" className="panel panel-info collapse" aria-expanded="false"> | |
<div className="panel-heading"> | |
<i className="fa fa-info-circle m-r-xs" /> | |
Help | |
</div> | |
<div className="panel-body"> | |
<p> | |
<strong>Resources:</strong> Assign resources associated with this product. When the | |
capacity type is based on Product or Variants these resources will be added to the | |
booking automatically when it is created but their bookings do not count towards | |
availability. If the capacity is based on resources then any bookings for the resource | |
count towards availability. | |
</p> | |
<p> | |
<strong>Locations:</strong> Choose the locations this product applies to. Location | |
information such as address, map & email can be used in reminders. Locations are | |
created via the configuration menu. | |
</p> | |
</div> | |
</div> | |
</div> | |
<div className="col-sm-6"> | |
{this.isCapacityResourceBased() && this.props.resourceQueryBuilderEnabled ? ( | |
<ProductResources | |
resourceOptions={this.props.resourceOptions} | |
query={this.props.product.resource_capacity_json} | |
onChange={this.props.actions.onResourcesSet} | |
/> | |
) : ( | |
<FormNested | |
name={this.props.resourceQueryBuilderEnabled ? "resource_capacity_json" : "resource_constraints_attributes"} | |
placeholder="Choose a Resource" | |
values={this.props.product.resource_constraints_attributes} | |
onChange={e => this.props.actions.onResourcesSet(e, this.isCapacityResourceBased())} | |
options={this.props.resourceOptions} | |
nestedName="resource_id" | |
/> | |
)} | |
</div> | |
<div className="hr-line-dashed m-t-sm m-b-sm visible-xs" /> | |
<div className="col-sm-6"> | |
<h3 className="m-t-none m-b visible-xs">Locations</h3> | |
<FormNested | |
name="product_locations_attributes" | |
placeholder="Choose a Location" | |
values={this.props.product.product_locations_attributes} | |
onChange={this.props.actions.onLocationsSet} | |
options={this.props.locationOptions} | |
availableOptions={this.props.multiLocationInventoryEnabled ? [] : this.props.availableLocations} | |
nestedName="location_id" | |
/> | |
</div> | |
</div> | |
<div className="hr-line-dashed m-t-sm m-b-sm" /> | |
{/* === DATEPICKER RESTRICTIONS SECTION === */} | |
{Utils.hideForProfiles([COURSE_PROFILE], product.profile) && ( | |
<div> | |
<div className="row"> | |
<div className="col-sm-12"> | |
<div className="help-icon"> | |
<a | |
className="btn" | |
role="button" | |
data-toggle="collapse" | |
href="#datepicker-restrictions-help" | |
title="Help" | |
> | |
<i className="fa fa-info-circle" /> | |
</a> | |
</div> | |
<h3 className="m-t-none m-b">Datepicker</h3> | |
<div | |
id="datepicker-restrictions-help" | |
className="panel panel-info collapse" | |
aria-expanded="false" | |
> | |
<div className="panel-heading"> | |
<i className="fa fa-info-circle m-r-xs" /> | |
Help | |
</div> | |
<div className="panel-body"> | |
<p> | |
<strong>Cutoff Days</strong> | |
: First day (from today) that will be available in the | |
datepicker. Set to 0 for today, 1 for tomorrow etc. | |
</p> | |
<p> | |
<strong>Future Days</strong> | |
: Maximum number of days to allow from today. Must be greater | |
than or equal to Cutoff Days. | |
</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div className="row"> | |
<div className="col-sm-6"> | |
<div className="no-padding"> | |
<label>Cutoff Days</label> | |
<FormInputWithTime | |
name="mindate" | |
value={this.props.product.mindate} | |
inputTimeInSec | |
outputTimeInSec | |
onChange={this.props.actions.onMindateSet} | |
/> | |
</div> | |
</div> | |
<div className="col-sm-6"> | |
<div className="col-md-3 no-padding"> | |
<label>Future Days</label> | |
<FormNumberInput | |
name="maxdate" | |
value={this.props.product.maxdate} | |
onChange={this.props.actions.onMaxdateSet} | |
/> | |
</div> | |
</div> | |
</div> | |
<div className="hr-line-dashed m-t-sm m-b-sm" /> | |
</div> | |
)} | |
{/* === CALENDAR SETTINGS SECTION === */} | |
<div className="row"> | |
<div className="col-sm-12"> | |
<h3 className="m-t-none m-b">Calendar Settings</h3> | |
</div> | |
</div> | |
<div className="row"> | |
<div className="col-sm-3"> | |
<div className="form-group"> | |
<label>Text Color</label> | |
<FormSelect | |
value={this.props.product.text_color} | |
options={this.props.colorOptions} | |
includeBlank | |
capitalize | |
onChange={this.props.actions.onTextColorSet} | |
/> | |
</div> | |
</div> | |
<div className="col-sm-3"> | |
<div className="form-group"> | |
<label>Background Color</label> | |
<FormSelect | |
value={this.props.product.background_color} | |
options={this.props.colorOptions} | |
includeBlank | |
capitalize | |
onChange={this.props.actions.onBackgroundColorSet} | |
/> | |
</div> | |
</div> | |
<div className="col-sm-3"> | |
<div className="form-group"> | |
<label>Border Color</label> | |
<FormSelect | |
value={this.props.product.border_color} | |
options={this.props.colorOptions} | |
includeBlank | |
capitalize | |
onChange={this.props.actions.onBorderColorSet} | |
/> | |
</div> | |
</div> | |
<div className="col-sm-3"> | |
<div className="form-group"> | |
<label>Preview</label> | |
<div> | |
<p | |
style={{ | |
borderColor: this.props.product.border_color, | |
color: this.props.product.text_color, | |
backgroundColor: this.props.product.background_color, | |
}} | |
className="p-xxs border-left-right border-top-bottom" | |
> | |
1 pm - 2 pm | |
{' '} | |
{this.props.product.product_title} | |
</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
{Utils.displayForProfiles([ACTIVITY_PROFILE, CLASS_PROFILE], product.profile) && ( | |
<div className="row"> | |
<div className="col-sm-12"> | |
<div className="form-group"> | |
<div className="checkbox checkbox-inline"> | |
<FormCheckbox | |
value={this.props.product.visible_in_calendar} | |
id="visible_in_calendar" | |
name="visible_in_calendar" | |
onChange={this.props.actions.onVisibleInCalendarSet} | |
/> | |
<label htmlFor="visible_in_calendar">Show in Store Calendar?</label> | |
</div> | |
</div> | |
</div> | |
</div> | |
)} | |
{/*= == EMAIL NOTIFICATION SECTION === */} | |
{Utils.displayForProfiles([ACTIVITY_PROFILE, CLASS_PROFILE], product.profile) && ( | |
<div> | |
<div className="hr-line-dashed m-t-sm m-b-sm" /> | |
<div className="row"> | |
<div className="col-sm-12"> | |
<div className="help-icon"> | |
<a | |
className="btn" | |
role="button" | |
data-toggle="collapse" | |
href="#enrollment-email-help" | |
title="Help" | |
> | |
<i className="fa fa-info-circle" /> | |
</a> | |
</div> | |
<h3 className="m-t-none m-b">{I18n.t('products.enrollment.email.title')}</h3> | |
<div id="enrollment-email-help" className="panel panel-info collapse" aria-expanded="false"> | |
<div className="panel-heading"> | |
<i className="fa fa-info-circle m-r-xs" /> | |
Help | |
</div> | |
<div className="panel-body"> | |
<p>{I18n.t('products.enrollment.email.help')}</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div className="row"> | |
<div className="col-sm-12"> | |
<div className="form-group"> | |
<label>{I18n.t('products.enrollment.email.label')}</label> | |
<FormInput | |
name="headsup_email_addresses" | |
value={this.props.product.headsup_email_addresses} | |
onChange={val => this.props.actions.onProductChange('headsup_email_addresses', val)} | |
/> | |
</div> | |
</div> | |
</div> | |
</div> | |
)} | |
{/* === SCHEDULE SECTION === */} | |
{Utils.displayForProfiles([ACTIVITY_PROFILE, CLASS_PROFILE, GENERAL_PROFILE], product.profile) | |
&& this.props.product.scheduled && ( | |
<div> | |
<div className="hr-line-dashed m-t-sm m-b-sm" /> | |
<ScheduleForm | |
title="" | |
allDay={this.props.product.all_day} | |
schedule={this.props.product.schedule_attributes} | |
onScheduleUpdateByIndex={this.props.actions.onProductScheduleUpdateByIndex} | |
onNewScheduleAdd={this.props.actions.onNewProductScheduleAdd} | |
locations={this.props.locationOptions} | |
eventColors={{ | |
textColor: this.props.product.text_color, | |
backgroundColor: this.props.product.background_color, | |
borderColor: this.props.product.border_color, | |
}} | |
blackoutsSettings={{ display: true, product_id: this.props.product.id }} | |
openingHoursSettings={{ display: true, product_id: this.props.product.id }} | |
duration={this.getScheduleDuration()} | |
productLocations={this.props.product.product_locations_attributes} | |
/> | |
</div> | |
)} | |
{/* === COURSE SECTION === */} | |
{Utils.displayForProfiles([COURSE_PROFILE], product.profile) | |
&& this.props.product.scheduled && ( | |
<div> | |
<div className="hr-line-dashed m-t-sm m-b-sm" /> | |
<ProductTerms | |
items={this.props.product.terms_attributes} | |
onTermAdd={this.props.actions.onTermAdd} | |
onTermChange={this.props.actions.onTermSet} | |
productId={this.props.product.id} | |
goToPage={this.goToPage.bind(this)} | |
termEvents={this.props.term_events} | |
/> | |
</div> | |
)} | |
</div> | |
</Tab> | |
<Tab title="Settings"> | |
<div id="settings"> | |
<div className="row"> | |
<div className="col-sm-12"> | |
<div className="row"> | |
<div className="col-sm-12"> | |
<div className="help-icon"> | |
<a className="btn" role="button" data-toggle="collapse" href="#external-calendar-help" title="Help"> | |
<i className="fa fa-info-circle" /> | |
</a> | |
</div> | |
<h3 className="m-t-none m-b">External Calendars</h3> | |
<div id="external-calendar-help" className="panel panel-info collapse" aria-expanded="false"> | |
<div className="panel-heading"> | |
<i className="fa fa-info-circle m-r-xs" /> | |
Help | |
</div> | |
<div className="panel-body"> | |
<p> | |
You can synchronize bookings from other calendars that support exporting to ical. | |
Just enter the ical urls corresponding to each calendar that the product syncs with. | |
Times that are busy | |
in the other calendar will count as busy time towards this product. BookThatApp will | |
check every hour for updates. | |
</p> | |
<p><strong>Google Calendar</strong>: To sync a Google Calendar please see the 'See | |
your calendar (view only)' section | |
in <a href='//support.google.com/calendar/answer/37648?hl=en' target='blank'>this | |
support article</a>.</p> | |
<p><strong>Facebook Events</strong>: Please see <a | |
href='//www.facebook.com/help/work/897976446932009' target='blank'>How do I export | |
my events</a>.</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div className="row"> | |
<div className="col-sm-12"> | |
<InputFeeds | |
inputFeeds={this.props.product.input_feeds} | |
productId={this.props.product.id} | |
inputFeedsValidationErrors={this.props.inputFeedsValidationErrors} | |
onAddInputFeed={this.props.actions.onAddInputFeed} | |
onDeleteInputFeed={this.props.actions.onDeleteInputFeed} | |
onSetInputFeedName={this.props.actions.onSetInputFeedName} | |
onSetInputFeedUrl={this.props.actions.onSetInputFeedUrl} | |
onImportInputFeed={this.props.actions.onImportInputFeed} | |
/> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</Tab> | |
</Tabs> | |
</div> | |
{' '} | |
{/* end ibox-content */} | |
<div className="ibox-footer"> | |
<button | |
className="btn btn-sm btn-primary" | |
type="submit" | |
ref="saveButton" | |
onClick={this.onSubmit.bind(this)} | |
disabled={this.props.productIsLoading} | |
> | |
Save | |
</button> | |
<a href="/admin/products" className="btn btn-sm btn-white"> | |
Close | |
</a> | |
{window.bta.isAdmin && this.props.product.id && ( | |
<button | |
type="submit" | |
disabled={this.props.productIsLoading} | |
onClick={this.handleResyncClick} | |
className="btn btn-sm label-warning"> | |
Resync | |
</button> | |
)} | |
<FormDeleteButtonContainer | |
id={this.props.product.id} | |
itemLabel="product" | |
redirectUrl="/admin/products" | |
url={`/admin/products/${this.props.product.id}`} | |
/> | |
</div> | |
</div> | |
</div> | |
</div> | |
<CopyProductForm | |
productId={this.props.product.external_id} | |
onProductPropertiesCopy={this.props.actions.onProductPropertiesCopy} | |
onSubmit={this.onSubmit.bind(this)} | |
/> | |
</div> | |
); | |
} | |
} | |
export default ProductForm; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment