finish implementing card layout
This commit is contained in:
		
							parent
							
								
									46be8e8629
								
							
						
					
					
						commit
						becae553b8
					
				
					 7 changed files with 157 additions and 39 deletions
				
			
		
							
								
								
									
										20
									
								
								src/App.vue
									
										
									
									
									
								
							
							
						
						
									
										20
									
								
								src/App.vue
									
										
									
									
									
								
							| 
						 | 
					@ -1,9 +1,18 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div id="app">
 | 
					    <div id="app">
 | 
				
			||||||
        <Navbar/>
 | 
					        <Navbar/>
 | 
				
			||||||
 | 
					        <div class="container-fluid px-xl-5 mt-3">
 | 
				
			||||||
        <div class="container mt-2">
 | 
					            <div class="row" v-if="layout === 'table'">
 | 
				
			||||||
            <Table
 | 
					                <div class="col-xl-8 offset-xl-2">
 | 
				
			||||||
 | 
					                    <Table
 | 
				
			||||||
 | 
					                        :columns="['uid', 'description', 'box', 'image']"
 | 
				
			||||||
 | 
					                        :items="loadedItems"
 | 
				
			||||||
 | 
					                        :keyName="'uid'"
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <Cards
 | 
				
			||||||
 | 
					                v-if="layout === 'cards'"
 | 
				
			||||||
                :columns="['uid', 'description', 'box', 'image']"
 | 
					                :columns="['uid', 'description', 'box', 'image']"
 | 
				
			||||||
                :items="loadedItems"
 | 
					                :items="loadedItems"
 | 
				
			||||||
                :keyName="'uid'"
 | 
					                :keyName="'uid'"
 | 
				
			||||||
| 
						 | 
					@ -15,12 +24,13 @@
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
import Table from '@/components/Table';
 | 
					import Table from '@/components/Table';
 | 
				
			||||||
import Navbar from '@/components/Navbar';
 | 
					import Navbar from '@/components/Navbar';
 | 
				
			||||||
 | 
					import Cards from '@/components/Cards';
 | 
				
			||||||
import { mapState } from 'vuex';
 | 
					import { mapState } from 'vuex';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
  name: 'app',
 | 
					  name: 'app',
 | 
				
			||||||
  components: { Navbar, Table },
 | 
					  components: { Navbar, Table, Cards },
 | 
				
			||||||
  computed: mapState(['loadedItems'])
 | 
					  computed: mapState(['loadedItems', 'layout'])
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										72
									
								
								src/components/Cards.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/components/Cards.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,72 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div class="row">
 | 
				
			||||||
 | 
					        <div class="col-lg-3 col-xl-2">
 | 
				
			||||||
 | 
					            <div class="card bg-dark text-light mb-2" id="filters">
 | 
				
			||||||
 | 
					                <div class="card-body">
 | 
				
			||||||
 | 
					                    <h5 class="card-title text-info">Sort & Filter</h5>
 | 
				
			||||||
 | 
					                    <div class="form-group" v-for="(column, index) in columns" :key="index">
 | 
				
			||||||
 | 
					                        <label>{{ column }}</label>
 | 
				
			||||||
 | 
					                        <div class="input-group">
 | 
				
			||||||
 | 
					                            <div class="input-group-prepend">
 | 
				
			||||||
 | 
					                                <button
 | 
				
			||||||
 | 
					                                    :class="[ 'btn', column === sortBy ? 'btn-outline-info' : 'btn-outline-secondary' ]"
 | 
				
			||||||
 | 
					                                    type="button"
 | 
				
			||||||
 | 
					                                    @click="toggleSort(column)">
 | 
				
			||||||
 | 
					                                    <font-awesome-icon :icon="getSortIcon(column)"/>
 | 
				
			||||||
 | 
					                                </button>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                            <input
 | 
				
			||||||
 | 
					                                type="text"
 | 
				
			||||||
 | 
					                                class="form-control"
 | 
				
			||||||
 | 
					                                placeholder="filter"
 | 
				
			||||||
 | 
					                                :value="filters[column]"
 | 
				
			||||||
 | 
					                                @input="setFilter(column, $event.target.value)"
 | 
				
			||||||
 | 
					                            >
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="col-lg-9 col-xl-8">
 | 
				
			||||||
 | 
					            <transition-group name="card-list" tag="div" class="card-columns">
 | 
				
			||||||
 | 
					                <div class="card-list-item card bg-dark text-light" v-for="item in internalItems" :key="item.uid">
 | 
				
			||||||
 | 
					                    <img
 | 
				
			||||||
 | 
					                        :src="`https://picsum.photos/id/${item.uid + 50}/200/200`"
 | 
				
			||||||
 | 
					                        alt="item"
 | 
				
			||||||
 | 
					                        class="card-img-top img-fluid"
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                    <div class="card-body">
 | 
				
			||||||
 | 
					                        <h6 class="card-title">{{ item.description }}</h6>
 | 
				
			||||||
 | 
					                        <h6 class="card-subtitle text-secondary">uid: {{ item.uid }} box: {{ item.box }}</h6>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </transition-group>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					import DataContainer from '@/mixins/data-container';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					  name: 'Cards',
 | 
				
			||||||
 | 
					  mixins: [DataContainer],
 | 
				
			||||||
 | 
					  methods: {
 | 
				
			||||||
 | 
					    random: () => Math.floor((Math.random() * 500) + 300)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style>
 | 
				
			||||||
 | 
					    .card-list-item {
 | 
				
			||||||
 | 
					        transition: all 1s;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .card-list-enter, .card-list-leave-to {
 | 
				
			||||||
 | 
					        opacity: 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .card-list-leave-active {
 | 
				
			||||||
 | 
					        position: absolute;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -15,6 +15,14 @@
 | 
				
			||||||
            <button type="button" class="btn mx-1 text-nowrap" v-for="(button, index) in buttons" v-bind:key="index" :class="['btn-' + button.color]">
 | 
					            <button type="button" class="btn mx-1 text-nowrap" v-for="(button, index) in buttons" v-bind:key="index" :class="['btn-' + button.color]">
 | 
				
			||||||
                <font-awesome-icon :icon="button.icon"/><span class="d-none d-md-inline"> {{ button.title }}</span>
 | 
					                <font-awesome-icon :icon="button.icon"/><span class="d-none d-md-inline"> {{ button.title }}</span>
 | 
				
			||||||
            </button>
 | 
					            </button>
 | 
				
			||||||
 | 
					            <div class="btn-group btn-group-toggle">
 | 
				
			||||||
 | 
					                <button :class="['btn', 'btn-info', { active: layout === 'cards' }]" @click="setLayout('cards')">
 | 
				
			||||||
 | 
					                    <font-awesome-icon icon="th"/>
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					                <button :class="['btn', 'btn-info', { active: layout === 'table' }]" @click="setLayout('table')">
 | 
				
			||||||
 | 
					                    <font-awesome-icon icon="list"/>
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
 | 
					        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
 | 
				
			||||||
| 
						 | 
					@ -38,7 +46,7 @@
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
import { mapState, mapActions } from 'vuex';
 | 
					import { mapState, mapActions, mapMutations } from 'vuex';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
| 
						 | 
					@ -48,14 +56,14 @@ export default {
 | 
				
			||||||
    buttons: [
 | 
					    buttons: [
 | 
				
			||||||
      { title: 'Add', icon: 'plus', color: 'success' },
 | 
					      { title: 'Add', icon: 'plus', color: 'success' },
 | 
				
			||||||
      { title: 'Refresh', icon: 'sync-alt', color: 'primary' },
 | 
					      { title: 'Refresh', icon: 'sync-alt', color: 'primary' },
 | 
				
			||||||
      { title: 'Placeholder', icon: 'cat', color: 'warning' },
 | 
					 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
  }),
 | 
					  }),
 | 
				
			||||||
  computed: {
 | 
					  computed: {
 | 
				
			||||||
    ...mapState(['events', 'activeEvent'])
 | 
					    ...mapState(['events', 'activeEvent', 'layout']),
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  methods: {
 | 
					  methods: {
 | 
				
			||||||
    ...mapActions(['changeEvent'])
 | 
					    ...mapActions(['changeEvent']),
 | 
				
			||||||
 | 
					    ...mapMutations(['setLayout'])
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,10 +3,10 @@
 | 
				
			||||||
        <thead>
 | 
					        <thead>
 | 
				
			||||||
            <tr>
 | 
					            <tr>
 | 
				
			||||||
                <th scope="col" v-for="(column, index) in columns" :key="index">
 | 
					                <th scope="col" v-for="(column, index) in columns" :key="index">
 | 
				
			||||||
                    <button class="btn text-light" v-on:click="toggleSort(column)">
 | 
					                    <button class="btn text-light" @click="toggleSort(column)">
 | 
				
			||||||
                        {{ column }}
 | 
					                        {{ column }}
 | 
				
			||||||
                        <span :class="{ 'text-info': column === sortBy }">
 | 
					                        <span :class="{ 'text-info': column === sortBy }">
 | 
				
			||||||
                            <font-awesome-icon :icon="sortIcon(column)"/>
 | 
					                            <font-awesome-icon :icon="getSortIcon(column)"/>
 | 
				
			||||||
                        </span>
 | 
					                        </span>
 | 
				
			||||||
                    </button>
 | 
					                    </button>
 | 
				
			||||||
                </th>
 | 
					                </th>
 | 
				
			||||||
| 
						 | 
					@ -21,34 +21,11 @@
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
import * as R from 'ramda';
 | 
					import DataContainer from '@/mixins/data-container';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
  name: 'Table',
 | 
					  name: 'Table',
 | 
				
			||||||
  props: ['columns', 'items', 'keyName'],
 | 
					  mixins: [DataContainer]
 | 
				
			||||||
  data: (self) => ({
 | 
					 | 
				
			||||||
    sortBy: self.keyName,
 | 
					 | 
				
			||||||
    ascend: true
 | 
					 | 
				
			||||||
  }),
 | 
					 | 
				
			||||||
  computed: {
 | 
					 | 
				
			||||||
    internalItems() {
 | 
					 | 
				
			||||||
      const sortByOrd = R.sortBy(R.prop(this.sortBy));
 | 
					 | 
				
			||||||
      const sorted = sortByOrd(this.items, [this.sortBy]);
 | 
					 | 
				
			||||||
      return this.ascend ? sorted : R.reverse(sorted);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  methods: {
 | 
					 | 
				
			||||||
    sortIcon(column) {
 | 
					 | 
				
			||||||
      if (column !== this.sortBy) return 'sort';
 | 
					 | 
				
			||||||
      if (this.ascend) return 'sort-up';
 | 
					 | 
				
			||||||
      return 'sort-down';
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    toggleSort(column) {
 | 
					 | 
				
			||||||
      if (column === this.sortBy)
 | 
					 | 
				
			||||||
        this.ascend = !this.ascend;
 | 
					 | 
				
			||||||
      this.sortBy = column;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,10 +9,10 @@ import 'bootstrap/dist/js/bootstrap.min.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// fontawesome
 | 
					// fontawesome
 | 
				
			||||||
import { library } from '@fortawesome/fontawesome-svg-core';
 | 
					import { library } from '@fortawesome/fontawesome-svg-core';
 | 
				
			||||||
import { faPlus, faCheckCircle, faEdit, faTrash, faCat, faSyncAlt, faSort, faSortUp, faSortDown } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faPlus, faCheckCircle, faEdit, faTrash, faCat, faSyncAlt, faSort, faSortUp, faSortDown, faTh, faList } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
 | 
					import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
library.add(faPlus, faCheckCircle, faEdit, faTrash, faCat, faSyncAlt, faSort, faSortUp, faSortDown);
 | 
					library.add(faPlus, faCheckCircle, faEdit, faTrash, faCat, faSyncAlt, faSort, faSortUp, faSortDown, faTh, faList);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Vue.component('font-awesome-icon', FontAwesomeIcon);
 | 
					Vue.component('font-awesome-icon', FontAwesomeIcon);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										39
									
								
								src/mixins/data-container.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/mixins/data-container.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,39 @@
 | 
				
			||||||
 | 
					import * as R from 'ramda';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					  props: ['columns', 'items', 'keyName'],
 | 
				
			||||||
 | 
					  data: (self) => ({
 | 
				
			||||||
 | 
					    sortBy: self.keyName,
 | 
				
			||||||
 | 
					    ascend: true,
 | 
				
			||||||
 | 
					    filters: R.fromPairs(self.columns.map(column => [column, '']))
 | 
				
			||||||
 | 
					  }),
 | 
				
			||||||
 | 
					  computed: {
 | 
				
			||||||
 | 
					    internalItems() {
 | 
				
			||||||
 | 
					      const filtered = this.items.filter(item => this.columns
 | 
				
			||||||
 | 
					        .map(column => {
 | 
				
			||||||
 | 
					          const field  = item[column] + '';
 | 
				
			||||||
 | 
					          const filter = this.filters[column];
 | 
				
			||||||
 | 
					          return field.includes(filter);
 | 
				
			||||||
 | 
					        }).reduce((acc, nxt) => acc && nxt, true)
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      const sortByOrd = R.sortBy(R.prop(this.sortBy));
 | 
				
			||||||
 | 
					      const sorted = sortByOrd(filtered, [this.sortBy]);
 | 
				
			||||||
 | 
					      return this.ascend ? sorted : R.reverse(sorted);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  methods: {
 | 
				
			||||||
 | 
					    getSortIcon(column) {
 | 
				
			||||||
 | 
					      if (column !== this.sortBy) return 'sort';
 | 
				
			||||||
 | 
					      if (this.ascend) return 'sort-up';
 | 
				
			||||||
 | 
					      return 'sort-down';
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    toggleSort(column) {
 | 
				
			||||||
 | 
					      if (column === this.sortBy)
 | 
				
			||||||
 | 
					        this.ascend = !this.ascend;
 | 
				
			||||||
 | 
					      this.sortBy = column;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    setFilter(column, filter) {
 | 
				
			||||||
 | 
					      this.filters[column] = filter;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -7,6 +7,7 @@ export default new Vuex.Store({
 | 
				
			||||||
  state: {
 | 
					  state: {
 | 
				
			||||||
    events: ['35c3', 'camp19', '36c3'],
 | 
					    events: ['35c3', 'camp19', '36c3'],
 | 
				
			||||||
    activeEvent: '36c3',
 | 
					    activeEvent: '36c3',
 | 
				
			||||||
 | 
					    layout: 'cards',
 | 
				
			||||||
    loadedItems: [
 | 
					    loadedItems: [
 | 
				
			||||||
      { uid: 1, description: 'sleeping bag', box: 7, image: 41 },
 | 
					      { uid: 1, description: 'sleeping bag', box: 7, image: 41 },
 | 
				
			||||||
      { uid: 2, description: 'tent', box: 7, image: 23 },
 | 
					      { uid: 2, description: 'tent', box: 7, image: 23 },
 | 
				
			||||||
| 
						 | 
					@ -14,16 +15,27 @@ export default new Vuex.Store({
 | 
				
			||||||
      { uid: 4, description: 'power supply black', box: 5, image: 62 },
 | 
					      { uid: 4, description: 'power supply black', box: 5, image: 62 },
 | 
				
			||||||
      { uid: 5, description: 'pullover yellow "pesthörnchen"', box: 5, image: 84 },
 | 
					      { uid: 5, description: 'pullover yellow "pesthörnchen"', box: 5, image: 84 },
 | 
				
			||||||
      { uid: 6, description: '"blue black second skin"', box: 6, image: 72 },
 | 
					      { uid: 6, description: '"blue black second skin"', box: 6, image: 72 },
 | 
				
			||||||
      { uid: 7, description: '"the bike blog" bottle orange', box: 6, image: 71 }
 | 
					      { uid: 7, description: '"the bike blog" bottle orange', box: 6, image: 71 },
 | 
				
			||||||
 | 
					      { uid: 8, description: 'tshirt guad3c', box: 6, image: 26 },
 | 
				
			||||||
 | 
					      { uid: 9, description: 'power supply dell', box: 6, image: 74 },
 | 
				
			||||||
 | 
					      { uid: 10, description: 'blanket green blue', box: 6, image: 25 },
 | 
				
			||||||
 | 
					      { uid: 11, description: 'cap "ega"', box: 6, image: 71 },
 | 
				
			||||||
 | 
					      { uid: 12, description: 'water bottle blue "sistema"', box: 3, image: 12 },
 | 
				
			||||||
 | 
					      { uid: 13, description: 'sun hat black', box: 5, image: 1 },
 | 
				
			||||||
 | 
					      { uid: 14, description: 'toy truck', box: 6, image: 51 }
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  mutations: {
 | 
					  mutations: {
 | 
				
			||||||
    changeEvent(state, event) {
 | 
					    changeEvent(state, event) {
 | 
				
			||||||
      state.activeEvent = event;
 | 
					      state.activeEvent = event;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    setLayout(state, layout) {
 | 
				
			||||||
 | 
					      state.layout = layout;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  actions: {
 | 
					  actions: {
 | 
				
			||||||
    changeEvent({ commit }, event) {
 | 
					    changeEvent({ commit }, event) {
 | 
				
			||||||
 | 
					      // todo: load items from server
 | 
				
			||||||
      // todo: load items from server
 | 
					      // todo: load items from server
 | 
				
			||||||
      commit('changeEvent', event);
 | 
					      commit('changeEvent', event);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue