JSFiddle - React, Tailwind, and code Playground
HTML
<label><input type="checkbox" data-bind="checked: showInfo"/>Show Info</label>
<label><input type="checkbox" data-bind="checked: showBundleConfig"/>Show Bundle Config</label>
<label><input type="checkbox" data-bind="checked: showCustAssignment"/>Show Customer Assignment</label>
<label><input type="checkbox" data-bind="checked: showWizard"/>Show Wizard</label>
<div data-bind="visible: $root.showInfo">
Rules
<ul>
<li>Round Up Millis to nearest time increment (i.e 6s)</li>
<li>Then Round to nearest billing fraction (0.1p)</li>
<li>Then Add Connection Charge</li>
<li>When free minutes of a type go, it uses total free minutes</li>
<li>When all free minutes go, it bills</li>
<li>if a call is split over free minutes threshold, it's partially billed</li>
<li>And a connection charge added</li>
<li>Sessions are written every 60 minutes</li>
<li>Remaining credit is written every 2 seconds</li>
<li>Free minutes and reset of starts happens 23:59:39 on the last day of the month</li>
</ul>
Structure
<pre>
Bundle
|- Price Band
|- Call Rate
|- Price Break
e.g.
Bundle 1
|- Price Band A (0-5000 minutes)
|- Call Rate (07 mobiles)
|- Price Break 1 (0-60min 0.5p)
|- Price Break 2 (60+min 0.2p)
|- Call Rate (00 internaltional)
|- Price Break 1 (2.5p)
</pre>
</div>
<div data-bind="visible: $root.showBundleConfig">
<h2>Bundles</h2>
<div data-bind="foreach: bundles">
<div class="bundle-item">
<div class="bundle-name" data-bind="text: Name"></div>
<div data-bind="text: Description"></div>
<button data-bind="click: $parent.editBundle_click">Edit Bundle</button>
</div>
</div>
<div class="edit-bundle" data-bind="with: editingBundle">
<div class="formitem">
<h2>Editing:</h2>
<div class="title">Bundle</div>
<label class="name">Name:</label><input...
CSS
.bundle-item {
height: 100px;
background-color: #ccccf2;
border: 1px solid #ccccf2;
padding: 8px;
border-radius: 5px;
width: 160px;
margin: 5px;
list-style: none;
text-align: center;
}
.wizard .bundle-item:hover {
border: 1px solid black;
background-color: #e4ffd4;
}
.bundle-name {
font-weight: bold;
margin-bottom: 15px;
}
.edit-bundle .formitem {
background-color: white;
padding: 10px;
margin: 5px;
list-style: none;
}
.edit-bundle .formitem ul {
list-style: none;
padding-left:5px;
}
.edit-bundle .formitem label input {
margin-left: 10px;
}
.edit-bundle .formitem label {
width: 120px;
display: inline-block;
margin-right: 4px;
margin-bottom: 3px;
}
.edit-bundle .formitem input {
margin-bottom: 3px;
}
.edit-bundle .formitem .title {
font-weight: bold;
margin-bottom: 5px;
}
.edit-bundle .formitem.break {
background-color: #e0e0e0;
border-radius: 6px;
padding-bottom: 10px;
margin-bottom: 10px;
}
.edit-bundle .formitem.rating ul li {
background-color: #e7ecff;
padding: 5px;
border-radius: 5px;
margin-bottom: 10px;
}
.edit-bundle .formitem.break ul li {
background-color: #e2e2ff;
padding: 8px;
border-radius: 6px;
margin-bottom: 10px;
}
JavaScript
var Bundles = [
{
Name: ko.observable("Standard Bundle"),
Description: ko.observable("100 Minutes / Month"),
},
{
Name: ko.observable("Bigger Bundle"),
Description: ko.observable("0 Minutes / Month, higher rates"),
}
]
var RatingBands = [
{
Name: ko.observable("First 5k"),
Description: ko.observable("First 5000 Minutes"),
PriceBreakTotalCallLengthMillis: ko.observable(100000000)
}
]
var CallRates = [
{
Name: ko.observable("Mobile"),
Description: ko.observable("Mobile Calls"),
Prefix: ko.observable("07*"),
RatingCallType: ko.observable(3),
},
{
Name: ko.observable("National"),
Description: ko.observable("National Calls"),
Prefix: ko.observable("01*"),
RatingCallType: ko.observable(1),
ConnectionCharge: ko.observable(0.5),
}
];
var PriceBreaks = [
{
Name: ko.observable("First 20m"),
Description: ko.observable(""),
CostPerSecond: ko.observable(0.2),
MaxBillingLengthMillis: ko.observable(200000)
}
];
var CustomerPurchaseWizard = function(main) {
var self = this;
this.main = main;
this.chosenBundle = ko.observable();
this.creditAdd = ko.observable(20);
this.wizardStep = ko.observable(1);
this.templateForWiz = ko.pureComputed(function() {
return "wiz" + self.wizardStep();
});
this.chooseBundle_click = function(data, event) {
self.chosenBundle(data);
self.nextStep_click();
}
this.goToStart_click = function(data, event) {
var pre = parseFloat(main.remainingCredit()) + parseFloat(self.creditAdd());
console.log(pre);
main.remainingCredit(pre);
self.wizardStep(1);
}
this.nextStep_click = function() {
self.wizardStep(self.wizardStep() + 1);
}
}
var CustomerSelectorViewModel = function(bundles) {
this.selectedCustomerBundles = ko.observableArray(bundles || []);
}
var ViewModel = function() {
var self = this;
// more likely...