JSFiddle - React, Tailwind, and code Playground

by tidy

HTML

<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>
<script src="https://unpkg.com/[email protected]/min/moment.min.js"></script>
<script src="https://unpkg.com/[email protected]/lodash.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/flatpickr.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/flatpickr.min.css">
<div id="offer">
  <div id="info">
    <button @click="toggleJson" style="float: right;">JSON</button>
    <h2>Tuotteet</h2>
    <p>Demo tuotteen reaaliaikaisesta hintalaskennasta. Ohje:</p>
    <ol>
      <li>Aloita valitsemalla tuotteen "Esimerkki". Eri esimerkit viittaavat tilattuihin palveluihin.</li>
      <li>Valitse vuokrauksen alku ja päätös päivä</li>
      <li>Täytä hintakentät</li>
    </ol>
    <button @click="addProduct">Lisää tuote</button>
  </div>
  <product-row v-for="(product, idx) in products" 
               class="product"
               :idx="idx"
               :prod="product"
               v-on:update="updateProduct"
               v-on:remove="removeProduct"
  ></product-row>
  
  <pre id="json" v-text="productJson" :class="{ 'hidden': jsonHidden }"></pre>
  
</div>
<script type="text/x-template" id="product-row-template">

    <div class="row">
    
      <div class="desc">
        <b v-text="'Tuote ' + (idx + 1)"></b><br>
        <select v-model="prod.category">
          <option :value="null" disabled>Valitse esimerkki</option>
          <option v-for="cat in categories" :value="cat.id" v-text="cat.name"></option>
        </select>
      </div>

      <div class="prices">
      	<div>
        	<label>Vuokrauksen alku</label>
          <input type="text" :class="'datepicker-' + prod.id" v-model="prod.dateStart" placeholder="2017-05-01" @change="setPriceUnit">
        </div>
      	<div>
        	<label>Vuokrauksen päätös</label>
        	<input type="text" :class="'datepicker-' + prod.id" v-model="prod.dateEnd" placeholder="2017-05-30" @change="setPriceUnit">
   ...

CSS

body {
  font-family: sans-serif;
}
#info {
  margin-bottom: 20px;
}
#json {
  width: 100%;
  border: 2px solid red;
  padding: 10px;
  box-sizing: border-box;
}
#json.hidden {
  display: none;
}
.row {
  display: flex;
  width: 100%;
  flex-direction: row;
  border-top: 1px solid black;
  margin-bottom: 10px;
  padding-top: 10px;
}
.row:first-child {
  border-top: 0;
}
.row > div {
  margin-left: 5px;
  padding-left: 10px;
  padding-right: 10px;
  border-right: 1px solid black;
}
.row > div:last-child {
  border: 0;
}
.row .prices {
  display: flex;
  flex-direction: column;
}
.row .prices > div {
  display: flex;
  flex-direction: column;
  padding-bottom: 10px;
}
.row .row-btn {
  flex-grow: 1;
  text-align: right;
}

JavaScript

// core packages
// https://unpkg.com/[email protected]/dist/vue.min.js
// https://unpkg.com/[email protected]/min/moment.min.js
// https://unpkg.com/[email protected]/lodash.min.js
// extras
// https://unpkg.com/[email protected]/dist/flatpickr.min.css
// https://unpkg.com/[email protected]/dist/flatpickr.min.js

var productDefaults = {
  id: null,
  category: null,
  dateStart: null,
  dateEnd: null,
  priceUnit: null,
  pricePerUnit: null,
  deliveryPrice: null,
  returnPrice: null,
  installPrice: null,
  uninstallPrice: null,
}

Vue.component('product-row', {

	template: '#product-row-template',
  
  data: function() {
  	return {
      categories: [
        { id: 'cat1', name: 'Nouto/palautus', delivery: false, install: false },
        { id: 'cat2', name: 'Toimitus', delivery: true, install: false },
        { id: 'cat3', name: 'Toimitus + Asennus', delivery: true, install: true },
      ],
      pricing: [
        { id: 'day', name: 'Päivähinta', range: 'päivä' },
        { id: 'week', name: 'Viikkohinta', range: 'viikko' },
        { id: 'month', name: 'Kuukausihinta', range: 'kuukausi' },
      ],
    }
  },
  
	props: {
  	idx: Number,
  	prod: Object,
  },
  
  watch: {
  	prod: {
    	handler: function(prod){
        this.$emit('update', this.idx, prod)
      },
      deep: true,
    },
  },
  
  computed: {
  
  	productCategory: function(){
    	if (!this.prod.category) {
      	return null
      }
      return _.find(this.categories, ['id', this.prod.category])
    },
  
  	rentDays: function(){
    	var me = this
      if (!me.prod.dateStart || !me.prod.dateEnd) {
      	return null
      }
      var days = moment(me.prod.dateEnd, 'YYYY-MM-DD')
      				.diff(moment(me.prod.dateStart, 'YYYY-MM-DD'), 'days')
      if (days < 1) {
      	return null
      }
      return days
    },
    
  	priceUnitByDays: function() {
    	var me = this
      if (!me.rentDays) {
      	return null
      }
      if (me.rentDays < 7) {
      	return 'day'
      } else if (me.rentDays...