Prechádzať zdrojové kódy

[ADD] initial commit

Gogs 7 rokov pred
commit
35c1a4b48c
9 zmenil súbory, kde vykonal 853 pridanie a 0 odobranie
  1. 38 0
      AddCard.vue
  2. 89 0
      Card.vue
  3. 73 0
      CardGrid.vue
  4. 124 0
      Cart.vue
  5. 151 0
      CartItem.vue
  6. 105 0
      Searcher.vue
  7. 102 0
      Spinner.vue
  8. 162 0
      Ticket.vue
  9. 9 0
      index.js

+ 38 - 0
AddCard.vue

@@ -0,0 +1,38 @@
+<template lang="pug">
+    .add-card(@click='onClick')
+        i.fa.fa-plus(aria-hidden='true')
+</template>
+
+<script>
+    export default {
+        methods: {
+            onClick(e) {
+                this.$emit('onClickAdd')
+            }
+        }
+    }
+</script>
+
+<style lang="sass">
+    @import '../../assets/variables'
+
+    .add-card
+        width: 130px
+        height: 160px
+        margin: 5px
+        border: 1px solid $app-border-color
+        display: inline-block
+        position: relative
+        &:hover
+            cursor: pointer
+        i
+            font-size: 36pt
+            margin: 0
+            border: none
+            position: absolute;
+            top: 50%
+            left: 50%
+            margin-right: -50%
+            transform: translate(-50%, -50%)
+            color: $app-main-color
+</style>

+ 89 - 0
Card.vue

@@ -0,0 +1,89 @@
+<template lang="pug">
+    .card(@click='onClick' :class="{ 'selected-card': isSelected }")
+        h2.card-title {{ title }}
+        img.card-image(:src="'data:image/png;base64,' + (image || 'iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAAAAACPAi4CAAAACXZwQWcAAABAAAAAQADq8/hgAAAEWklEQVRYw9WX6XKjRhCAef8HiySQvGt5vfZuEselOUAcEpe4GdI9MAgQOjb5k3SVyzY1801PX9OtNf9StP80QJR5miRpXtb/AFCnvmMySgmhlJn2Mal+BSBSj1NCGeNSGAMOd0/iQYCI95TAXnm+FCr/I2ZYPwJILEJhPaGm7flBFIW+Z5sUvwEivguovG7pMR0cV2e+BbYArF3cBqQclKfEvryvSB2KaHa6BYhgDSP7ZN7gmUNQCf86wCdgcBaKq04/cTzAuwbA/czKb8VdZYMSI8IAEOJ+XjTiFkF4SDjOARIIHLiBK+4E/xHOIdEloMSAAwZx7hEOBKIquwA4lFPbR/3uEhzCqSUmgBiwrGgeIlQm5b0zO0CN3yKw34QgQC4JKZqrGAFC0MpWvuwJ3V6hWD3BI5wchoDaBAumzYQgmsrd7ewZx5bosHIAAAtQp4+nXUuA+2yXy9Xyi4OsIorjauBLZQWtd0Gqrt3EvCXQlb4BMZYfsPP7cr0gvS4FaNw6Qus0ovtez8DZcYyHt8Wmk9XWdF+Mjf570Ke4q46UgAgUCtX55mKl/wSbsD83hrEE0VGJ1RrEWHz2aaXuIAEe7b3SNG/601oSzL/W20/T2r2uDNACARvjWelZQTTaCiCg2vSR1bzrsFgSQMk8SbPi8FWX+0GFbX2OXMarDoAmOGfo+wpXt7cwj4Hv+1n+rSMYW3HOfS4TAgHZIDIVYG38wNzchyB+kj4ZUwB4npw6ABokmgA2qz9kfbIkoWDLzQSQ0tbw2gA20kA/nmyqCHG8nmqQd2prbSKQZAIwnk5B5PSE/EWfACCUZGFSgHQKeE6DsCcExfc5wKEDRLMaJHBwTwA/zFzhOLBBPGODoCfEyYUb0XVBB1AGHXvho/SVDsSjF15QrtMG1xlpsDbCrCewj7UxAWAJSjsAlJOuHI0AX9Mi8IMgsJnMC2MMOJA2f7RhXI8AG/2LVxZZVlQWmKElnAFiT5nMH62L67Mb3lTmbIzVK3Uc9r6GvJAEyMa6d0KXP1oXliqbRPPzN0NvBcrBAmSpr37wlrB8GeRS6zkJECZVNRKeuLfty1C+wc/zp7TD9jVQN7DUDq2vkUEzfAymIl9uZ5iL1B0U1Rw7surmc4SE/sUBE3KaDB8Wd1QS7hJQga4Kayow2aAsXiV0L458HE/jx9UbPi33CIf+ITwDSnxM/IcIcAGIrHzaH+BX8Ky4awdq41nBZYsjG4/kEQLjg9Q5A9A1jJ7u3CJEa1OzmuvSKgubwPA24IT7WT7fJ5YmEtwbASWO2AkP94871WpPOCc8vmYHaORhv5lf75VrV3bD+9nZIrUJamhXN9v9kMlu3wonYVlGe9msU1/cGTgKpx0YmO2fsrKq66rMk8Bh7dd99sDIk+xxxsE5icqhqfsLflkz1pkbukSCBzI5bqG0EGrPGvfK2FeGDseRi1I5eVFuB8WvDp51FvsH13Fcz4+y6n86Oz8kfwPMD02INEiadQAAAABJRU5ErkJggg==')")
+        .card-description(v-if='!!description')
+            span {{ description }}
+</template>
+
+<script>
+    export default {
+        props: {
+            title: {
+                type: String,
+                default: 'Sin título'
+            },
+            image: {
+                type: String,
+                default: 'iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAAAAACPAi4CAAAACXZwQWcAAABAAAAAQADq8/hgAAAEWklEQVRYw9WX6XKjRhCAef8HiySQvGt5vfZuEselOUAcEpe4GdI9MAgQOjb5k3SVyzY1801PX9OtNf9StP80QJR5miRpXtb/AFCnvmMySgmhlJn2Mal+BSBSj1NCGeNSGAMOd0/iQYCI95TAXnm+FCr/I2ZYPwJILEJhPaGm7flBFIW+Z5sUvwEivguovG7pMR0cV2e+BbYArF3cBqQclKfEvryvSB2KaHa6BYhgDSP7ZN7gmUNQCf86wCdgcBaKq04/cTzAuwbA/czKb8VdZYMSI8IAEOJ+XjTiFkF4SDjOARIIHLiBK+4E/xHOIdEloMSAAwZx7hEOBKIquwA4lFPbR/3uEhzCqSUmgBiwrGgeIlQm5b0zO0CN3yKw34QgQC4JKZqrGAFC0MpWvuwJ3V6hWD3BI5wchoDaBAumzYQgmsrd7ewZx5bosHIAAAtQp4+nXUuA+2yXy9Xyi4OsIorjauBLZQWtd0Gqrt3EvCXQlb4BMZYfsPP7cr0gvS4FaNw6Qus0ovtez8DZcYyHt8Wmk9XWdF+Mjf570Ke4q46UgAgUCtX55mKl/wSbsD83hrEE0VGJ1RrEWHz2aaXuIAEe7b3SNG/601oSzL/W20/T2r2uDNACARvjWelZQTTaCiCg2vSR1bzrsFgSQMk8SbPi8FWX+0GFbX2OXMarDoAmOGfo+wpXt7cwj4Hv+1n+rSMYW3HOfS4TAgHZIDIVYG38wNzchyB+kj4ZUwB4npw6ABokmgA2qz9kfbIkoWDLzQSQ0tbw2gA20kA/nmyqCHG8nmqQd2prbSKQZAIwnk5B5PSE/EWfACCUZGFSgHQKeE6DsCcExfc5wKEDRLMaJHBwTwA/zFzhOLBBPGODoCfEyYUb0XVBB1AGHXvho/SVDsSjF15QrtMG1xlpsDbCrCewj7UxAWAJSjsAlJOuHI0AX9Mi8IMgsJnMC2MMOJA2f7RhXI8AG/2LVxZZVlQWmKElnAFiT5nMH62L67Mb3lTmbIzVK3Uc9r6GvJAEyMa6d0KXP1oXliqbRPPzN0NvBcrBAmSpr37wlrB8GeRS6zkJECZVNRKeuLfty1C+wc/zp7TD9jVQN7DUDq2vkUEzfAymIl9uZ5iL1B0U1Rw7surmc4SE/sUBE3KaDB8Wd1QS7hJQga4Kayow2aAsXiV0L458HE/jx9UbPi33CIf+ITwDSnxM/IcIcAGIrHzaH+BX8Ky4awdq41nBZYsjG4/kEQLjg9Q5A9A1jJ7u3CJEa1OzmuvSKgubwPA24IT7WT7fJ5YmEtwbASWO2AkP94871WpPOCc8vmYHaORhv5lf75VrV3bD+9nZIrUJamhXN9v9kMlu3wonYVlGe9msU1/cGTgKpx0YmO2fsrKq66rMk8Bh7dd99sDIk+xxxsE5icqhqfsLflkz1pkbukSCBzI5bqG0EGrPGvfK2FeGDseRi1I5eVFuB8WvDp51FvsH13Fcz4+y6n86Oz8kfwPMD02INEiadQAAAABJRU5ErkJggg=='
+            },
+            description: {
+                type: String,
+                default: ''
+            },
+            isSelected: {
+                type: Boolean,
+                default: true
+            }
+        },
+        methods: {
+            onClick() {
+                this.$emit('onClick')
+            }
+        } 
+    }
+</script>
+
+<style lang="sass">
+    @import '../../assets/variables'
+
+    .card
+        width: 130px
+        height: 160px
+        margin: 5px
+        border: 1px solid $app-border-color
+        display: inline-block
+        position: relative
+        &.selected-card
+            transition-duration: 300ms
+            border-bottom: 3px solid $app-main-color
+        &:hover
+            cursor: pointer
+        .card-title
+            width: 100%
+            height: 30px
+            font-size: 9pt
+            text-align: center
+            margin-top: 10px
+            position: absolute
+            top: 0
+        .card-image
+            width: 80px
+            height: 80px;
+            margin: 0
+            border: none
+            position: absolute
+            top: 50%
+            left: 50%
+            margin-right: -50%
+            transform: translate(-50%, -50%)
+        .card-description
+            width: 100%
+            height: 30px
+            padding-top: 5px
+            text-align: center
+            font-size: 10pt
+            font-weight: bold
+            background: $app-main-color
+            color: $app-bg-color
+            position: absolute
+            bottom: 0
+
+        @keyframes card-bubble
+            30%
+                transform: scaleX(0.75) scaleY(1.25)
+            40%
+                transform: scaleX(1.25) scaleY(0.75)
+            60%
+                transform: scaleX(0.85) scaleY(1.15)
+</style>

+ 73 - 0
CardGrid.vue

@@ -0,0 +1,73 @@
+<template lang="pug">
+    .card-grid-wrapper
+        .card-grid-loading(v-if='loading')
+            spinner(type='wave')
+        .card-grid(v-else)
+            add-card(v-if='canAdd' @onClickAdd='onClickAdd')
+            card(v-for='item in items' :key='item.id' :title='item.name' :image='item.imageMedium' :isSelected='item.id === selectedId' :description='getDescription(item)' @onClick='onClickCard(item)')
+</template>
+
+<script>
+    import AddCard from '@/components/common/AddCard'
+    import Card from '@/components/common/Card'
+    import Spinner from '@/components/common/Spinner'
+
+    export default {
+        props: {
+            items: {
+                type: Array,
+                default: []
+            },
+            canAdd: {
+                type: Boolean,
+                default: false
+            },
+            description: {
+                type: String,
+                default: ''
+            },
+            loading: {
+                type: Boolean,
+                default: false
+            }
+        },
+        components: {
+            AddCard,
+            Card,
+            Spinner
+        },
+        methods: {
+            getDescription(item) {
+                return (!!this.description && item[this.description]) || ''
+            },
+            onClickAdd() {
+                this.$emit('onAdd')
+            },
+            onClickCard(item) {
+                this.selectedId = item.id
+                this.$emit('onSelect', item)
+            }
+        },
+        data() {
+            return {
+                selectedId: -1
+            }
+        }
+    }
+</script>
+
+<style lang="sass">
+    .card-grid-wrapper
+        width: 100%
+        height: calc(100% - 100px)
+        margin-top: 15px
+        overflow-y: auto
+        .card-grid-loading
+            width: 100%
+            height: 100%
+            display: flex
+            align-items: center
+            justify-content: center
+        .card-grid
+            width: 100%
+</style>

+ 124 - 0
Cart.vue

@@ -0,0 +1,124 @@
+<template lang="pug">
+    .cart(:style='{ width: defaultOptions.layout.width, height: defaultOptions.layout.height }')
+        .cart-total
+            h2.currency-cart-total {{ total | currency(...defaultOptions.currency) }}
+        .cart-items-wrapper
+            transition-group(name='list' tag='ul' class='cart-items')
+                cart-item(v-for='(item, index) in items' :key='index' :index='index' :item='item' @onChange='onItemChanged' @onClickIncrement='onIncrementQty' @onClickDecrement='onDecrementQty' @onClickDelete='onDeleteItem' :options='defaultOptions.currency')
+</template>
+
+<script>
+    import CartItem from './CartItem'
+
+    export default {
+        components: {
+            CartItem
+        },
+        props: {
+            items: {
+                type: Array,
+                default: [],
+                required: true
+            },
+            options: {
+                type: Object || String,
+                default: null
+            }
+        },
+        methods: {
+            computeOptions(value) {
+                if (!value) {
+                    return
+                }
+
+                for(let key in value) {
+                    if(!this.defaultOptions.currency[key]) {
+                        continue
+                    }
+
+                    this.defaultOptions.currency[key] = value[key]
+                }
+            },
+            computeTotal() {
+                let sum = 0
+
+                for (let item of this.items) {
+                    sum = sum + ((item.price || 1) * (item.quantity || 1))
+                }
+
+                this.total = sum
+
+                this.$emit('onTotalComputed', this.total)
+            },
+            onItemChanged(item) {
+                this.computeTotal();
+            },
+            onIncrementQty(item) {
+                this.$emit('onIncrementQty', item)
+            },
+            onDecrementQty(item) {
+                this.$emit('onDecrementQty', item)
+            },
+            onDeleteItem(item) {
+                this.$emit('onDeleteItem', item)
+            }
+        },
+        watch: {
+            items() {
+                this.computeTotal()
+            },
+            options(value) {
+                this.computeOptions(value)
+            }
+        },
+        data() {
+            return {
+                total: 0,
+                defaultOptions: {
+                    currency: {
+                        symbol: '$',
+                        position: 'before',
+                        thousandsSeparator: '.',
+                        decimalPlaces: 2,
+                        decimalSeparator: ',' 
+                    },
+                    layout: {
+                        width: '300px',
+                        height: '100%'
+                    }
+                }
+            }
+        }
+    }
+</script>
+
+<style lang="sass">
+    @import '../../assets/variables'
+    .cart
+        border-left: 1px solid $app-border-color
+        padding-left: 10px
+        display: inline-block
+        vertical-align: top
+        .cart-total
+            width: 100%
+            height: 50px
+            .currency-cart-total
+                width: 100%
+                height: 50px
+                margin: 0
+                font-size: 30pt
+        .cart-items-wrapper
+            width: 100%
+            height: calc(100% - 100px)
+            overflow-y: auto
+            overflow-x: hidden
+            .cart-items
+                width: 100%
+                padding: 0
+                margin: 0
+            .list-enter-active, .list-leave-active
+                transition: all 0.3s
+            .list-enter, .list-leave-to
+                opacity: 0
+                transform: translateX(300px)
+</style>

+ 151 - 0
CartItem.vue

@@ -0,0 +1,151 @@
+<template lang="pug">
+    li.cart-item
+        h3.item-name {{ item.displayName }}
+        input.item-quantity(type='number' min='1' :value='item.quantity')
+        span.item-x x
+        span.item-price {{ (item.price || 1) | currency(...options) }}
+        span.item-equals =
+        span.item-subtotal {{ ((item.price || 1) * (item.quantity || 1)) | currency(...options) }}
+        .cart-item-options-wrapper
+            .cart-item-options
+                .cart-item-option(class='fa fa-plus' @click='onClickIncrement')
+                .cart-item-option(class='fa fa-minus' @click='onClickDecrement')
+                .cart-item-option(class='fa fa-trash' @click='onClickDelete')
+</template>
+
+<script>
+    export default {
+        props: {
+            index: {
+                type: Number,
+                default: -1,
+                required: true
+            },
+            item: {
+                type: Object,
+                default: null
+            },
+            options: {
+                type: Object,
+                default: {
+                    symbol: '$',
+                    position: 'before',
+                    thousandsSeparator: '.',
+                    decimalPlaces: 2,
+                    decimalSeparator: ',' 
+                }
+            }
+        },
+        watch: {
+            item: {
+                handler(value) {
+                    this.onChange(value)
+                },
+                deep: true,
+                immediate: true
+            }
+        },
+        methods: {
+            onChange(item) {
+                this.$emit('onChange', item)
+            },
+            onClickIncrement() {
+                this.$emit('onClickIncrement', this.item)
+            },
+            onClickDecrement() {
+                this.$emit('onClickDecrement', this.item)
+            },
+            onClickDelete() {
+                this.$emit('onClickDelete', this.item)
+            }
+        }
+    }
+</script>
+
+<style lang="sass">
+    @import '../../assets/variables'
+    .cart-item
+        width: 100%
+        height: 90px
+        list-style: none outside none
+        border-bottom: 1px solid $app-border-color
+        box-sizing: border-box
+        position: relative
+        &:nth-child(1)
+            border-top: 1px solid $app-border-color
+        &:hover
+            transition-duration: 1000ms
+            border-bottom: 2px solid $app-main-color
+        .item-name
+            width: 100%
+            height: 20px
+            margin: 10px 0 5px 0
+            float: left
+            font-size: 8pt
+            display: inline-block
+        .item-quantity
+            width: 50px
+            height: 28px
+            margin-top: 6px
+            text-align: right
+            float: left
+            display: inline-block
+        .item-x
+            width: 20px
+            height: 20px
+            margin-top: 12px
+            text-align: right
+            float: left
+            display: inline-block
+        .item-price
+            width: 80px
+            height: 20px
+            margin-top: 12px
+            text-align: right
+            float: left
+            display: inline-block
+        .item-equals
+            width: 20px
+            height: 20px
+            margin-top: 12px
+            text-align: center
+            float: left
+            display: inline-block
+        .item-subtotal
+            width: 100px
+            height: 20px
+            margin-top: 12px
+            text-align: right
+            font-weight: bold
+            display: inline-block
+        .cart-item-options-wrapper
+            width: 100%
+            height: 20px
+            position: absolute
+            bottom: 0
+            display: flex
+            justify-content: center
+            .cart-item-options
+                width: 90px
+                height: 20px
+                border: 1px solid #d3d3d3
+                border-bottom: none
+                display: flex
+                justify-content: center
+                .cart-item-option
+                    width: 18px
+                    height: 18px
+                    margin: 0 5px
+                    color: #666
+                    &:hover
+                        cursor: pointer
+                    &.fa
+                        padding-left: 2px
+                        line-height: 20px
+                        &.fa-plus:hover
+                            color: #2196f3
+                        &.fa-minus:hover
+                            color: #ffc107
+                        &.fa-trash:hover
+                            color: #f44336
+</style>

+ 105 - 0
Searcher.vue

@@ -0,0 +1,105 @@
+<template lang="pug">
+    input.searcher(type='search' :placeholder='placeholder' :style='{ width, height }' v-model='search')
+</template>
+
+<script>
+    import Fuse from 'fuse.js'
+
+    export default {
+        props: {
+            items: {
+                type: Array,
+                default: [],
+                required: true
+            },
+            placeholder: {
+                type: String,
+                default: 'Buscar...'
+            },
+            width: {
+                type: String,
+                default: '100%'
+            },
+            height: {
+                type: String,
+                default: '35px'
+            },
+            shouldSort: {
+                type: Boolean,
+                default: true
+            },
+            threshold: {
+                type: Number,
+                default: 0.4,
+            },
+            location: {
+                type: Number,
+                default: 0
+            },
+            distance: {
+                type: Number,
+                default: 100
+            },
+            maxPatternLength: {
+                type: Number,
+                default: 32
+            },
+            minMatchCharLength: {
+                type: Number,
+                default: 1
+            },
+            keys: {
+                type: Array,
+                default: [],
+                required: true
+            }
+        },
+        watch: {
+            items(values) {
+                this.fuse.setCollection(values)
+            },
+            search(value) {
+                this.performSearch(value.trim())
+            },
+            results(values) {
+                this.$emit('onSearch', values)
+            }
+        },
+        methods: {
+            initFuse() {
+                this.fuse = new Fuse(this.items, {
+                    shouldSort: this.shouldSort,
+                    threshold: this.threshold,
+                    location: this.location,
+                    distance: this.distance,
+                    maxPatternLength: this.maxPatternLength,
+                    minMatchCharLength: this.minMatchCharLength,
+                    keys: this.keys
+                })
+            },
+            performSearch(value) {
+                this.results = this.fuse.search(value)
+            }
+        },
+        data() {
+            return {
+                fuse: null,
+                search: '',
+                results: []
+            }
+        },
+        mounted() {
+            this.initFuse()
+        }
+    }
+</script>
+
+<style lang="sass">
+    .searcher
+        text-align: center
+        border-radius: 0 !important
+        font:
+            size: 11pt
+            style: normal
+            weight: bold
+</style>

+ 102 - 0
Spinner.vue

@@ -0,0 +1,102 @@
+<template lang="pug">
+    .spinner
+        .spinner-wave(v-if="type === 'wave'")
+            .spinner-rect.spinner-rect-1
+            .spinner-rect.spinner-rect-2
+            .spinner-rect.spinner-rect-3
+            .spinner-rect.spinner-rect-4
+            .spinner-rect.spinner-rect-5
+        .spinner-circle(v-if="type === 'circle'")
+            .spinner-circle-1.spinner-child
+            .spinner-circle-2.spinner-child
+            .spinner-circle-3.spinner-child
+            .spinner-circle-4.spinner-child
+            .spinner-circle-5.spinner-child
+            .spinner-circle-6.spinner-child
+            .spinner-circle-7.spinner-child
+            .spinner-circle-8.spinner-child
+            .spinner-circle-9.spinner-child
+            .spinner-circle-10.spinner-child
+            .spinner-circle-11.spinner-child
+            .spinner-circle-12.spinner-child
+</template>
+
+<script>
+    export default {
+        props: {
+            type: String,
+            default: 'wave'
+        }
+    }
+</script>
+
+<style lang="sass">
+    @import '../../assets/variables'
+    .spinner
+        .spinner-wave
+            $rect-count: 5
+            $animation-duration: 1000ms
+            $delay-range: 400ms
+
+            width: 50px
+            height: 40px
+            text-align: center
+            font-size: 10px
+            margin: 40px auto
+
+            .spinner-rect
+                width: 5px
+                height: 100%
+                background: $app-main-color
+                margin: 0 3px 0 0
+                display: inline-block
+                animation: spinner-rect-wave $animation-duration infinite ease-in-out
+
+            @for $i from 1 through $rect-count
+                .spinner-rect-#{$i}
+                    animation-delay: - $animation-duration + $delay-range / ($rect-count - 1) * ($i - 1)
+
+        .spinner-circle
+            $circle-count: 12
+            $animation-duration: 1200ms
+
+            margin: 40px auto
+            width: 40px
+            height: 40px
+            position: relative
+
+            .spinner-child
+                width: 100%
+                height: 100%
+                position: absolute
+                left: 0
+                top: 0
+            
+            .spinner-child:before
+                content: ''
+                display: block
+                margin: 0 auto
+                width: 15%
+                height: 15%
+                background: $app-main-color
+                border-radius: 100%
+                animation: spinner-circle-bounce $animation-duration infinite ease-in-out both
+            
+            @for $i from 2 through $circle-count
+                .spinner-circle#{$i}
+                    transform: rotate(360deg / $circle-count * ($i - 1))
+                .spinner-circle#{$i}:before
+                    animation-delay: - $animation-duration + $animation-duration / $circle-count * ($i - 1)
+    
+    @keyframes spinner-rect-wave
+        0%, 40%, 100%
+            transform: scaleY(0.4)
+        20%
+            transform: scaleY(1.0)
+
+    @keyframes spinner-circle-bounce
+        0%, 80%, 100%
+            transform: scale(0)
+        40%
+            transform: scale(1.0)
+</style>

+ 162 - 0
Ticket.vue

@@ -0,0 +1,162 @@
+<template lang="pug">
+    .ticket
+        .ticket-summary
+            .ticket-summary-header
+                h3 {{ companyName }}
+                table
+                    tbody
+                        tr
+                            td Producto
+                            td Precio
+                            td Cant
+                            td Subtotal
+            .ticket-items-wrapper
+                table
+                    tbody
+                        tr(v-for='item in items' :key='item.id')
+                            td {{ item.name }}
+                            td {{ item.price }}
+                            td {{ item.quantity }}
+                            td {{ (item.price || 0) * (item.quantity || 0) }}
+            .ticket-summary-footer
+                table
+                    tbody
+                        tr
+                            td Total:
+                            td {{ total | currency(...defaultCurrency) }}
+                        tr
+                            td Cliente:
+                            td {{ customerName }}
+</template>
+
+<script>
+    export default {
+        props: {
+            companyName: {
+                type: String,
+                default: ''
+            },
+            customerName: {
+                type: String,
+                default: ''
+            },
+            defaultCurrency: {
+                type: Object,
+                default: {
+                    symbol: '$',
+                    position: 'before',
+                    thousandsSeparator: '.',
+                    decimalPlaces: 2,
+                    decimalSeparator: ',' 
+                }
+            },
+            total: {
+                type: Number,
+                default: 0
+            },
+            items: {
+                type: [],
+                default: []
+            }
+        }
+    }
+</script>
+
+<style lang="sass">
+    @import '../../assets/variables'
+    .ticket
+        width: 500px
+        height: 100%
+        .ticket-summary
+            width: 350px
+            height: 450px
+            border: 1px solid $app-border-color
+            margin: auto
+            box-shadow: -2px 2px 5pc $app-border-color, 2px 2px 5px $app-border-color
+            position: relative
+            .ticket-summary-header, .ticket-summary-footer
+                width: 100%
+                position: absolute
+            .ticket-summary-header
+                height: 65px
+                top: 0
+                h3
+                    text-align: center
+                    font-size: 14pt
+                    margin: 0 15px
+                    padding: 30px 0 15px 0
+                    color: $app-dark-color
+                table
+                    width: 308px
+                    height: 30px
+                    margin: 0 20px
+                    font-size: 7.5pt
+                    font-weight: bold
+                    tbody
+                        tr
+                            line-height: 30px
+                            border-top: 1px solid $app-border-color
+                            border-bottom: 1px solid $app-border-color
+                            td
+                                &:nth-child(1)
+                                    width: 180px
+                                    text-align: left
+                    
+                                &:nth-child(2)
+                                    width: 50px
+                                    text-align: right
+                    
+                                &:nth-child(3)
+                                    width: 30px
+                                    text-align: right
+
+                                &:nth-child(4)
+                                    width: 50px
+                                    text-align: right
+            .ticket-items-wrapper
+                width: 310px
+                height: 280px
+                margin: 95px 20px 75px 20px
+                padding-top: 5px
+                overflow-y: auto
+                table
+                    width: 100%
+                    font-size: 7.5pt
+                    tbody
+                        tr
+                            height: 28px
+                            line-height: 30px
+
+                            td
+                                &:nth-child(1)
+                                    width: 180px
+                    
+                                &:nth-child(2)
+                                    width: 50px
+                                    text-align: right
+                    
+                                &:nth-child(3)
+                                    width: 30px
+                                    text-align: right
+
+                                &:nth-child(4)
+                                    width: 50px
+                                    text-align: right
+            .ticket-summary-footer
+                width: 348px
+                height: 75px
+                bottom: 0
+                padding: 15px 25px
+                background: $app-bg-color
+                table
+                    width: 100%
+                    tbody
+                        tr
+                            height: 25px
+                            line-height: 20px
+                            td
+                                &:nth-child(1)
+                                    font-weight: bold
+                                &:nth-child(2)
+                                    text-align: right
+</style>

+ 9 - 0
index.js

@@ -0,0 +1,9 @@
+import CardGrid from './CardGrid'
+import Cart from './Cart'
+import Searcher from './Searcher'
+
+export {
+    CardGrid,
+    Cart,
+    Searcher
+}