JSFiddle - React, Tailwind, and code Playground

HTML

<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/quasar@^1.0.0-beta.0/dist/quasar.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/swiper@4.0.7/dist/js/swiper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-awesome-swiper@3.1.2/dist/vue-awesome-swiper.js"></script>

<link href="https://fonts.googleapis.com/css?family=Material+Icons" rel="stylesheet" type="text/css" />
<link href="https://cdn.jsdelivr.net/npm/quasar@^1.0.0-beta.0/dist/quasar.min.css" rel="stylesheet" type="text/css" />
<link href="https://cdn.jsdelivr.net/npm/swiper@4.0.7/dist/css/swiper.min.css" rel="stylesheet">


<div id="app" class="q-pa-sm">
  <q-layout>
    <q-header>
      <q-toolbar>
        <q-btn icon="menu" flat></q-btn>
        <q-toolbar-title>Services</q-toolbar-title>
      </q-toolbar>
    </q-header>

    <q-page-container>
      <q-page>
        <swiper :options="swiperOption" class="swipeFullWrap">
          <swiper-slide class="swipeFull">
            <q-table row-key="id" :data="items" :columns="columns" class="oTable">
              <template v-slot:body="props">
                <q-tr
                  :props="props"
                  @mouseenter.native="props.row.hover = true"
                  @mouseleave.native="props.row.hover = false"
                >
                  <q-td v-for="{ name } in columns" :key="name" :props="props">
                    {{ props.row[name] }}
                  </q-td>
                </q-tr>
              </template>
            </q-table>
          </swiper-slide>
        </swiper>
      </q-page>
    </q-page-container>
  </q-layout>
</div>

CSS

.swipeFull {
  width: fit-content;
  min-width: 100%;
}
.swiper-container.swipeFullWrap > .swiper-wrapper {
  width: 100%;
}
.swipe-container {
  display: block;
  position: relative;
  width: fit-content;
  min-width: 100%;
}
.oTable {
  width: -webkit-fit-content;
  width: -moz-fit-content;
  width: fit-content;
  min-width: 100%;
  max-width: -webkit-fit-content;
  max-width: -moz-fit-content;
  max-width: fit-content;
}

JavaScript

Vue.use(VueAwesomeSwiper);

new Vue({
  el: '#app',
  data: () => ({
    columns: [],
    items: [],
    swiperOption: {
      direction: 'horizontal',
      slidesPerView: 'auto',
      virtualTranslate: true,
      on: {
        touchEnd() {
          this.LEFT = parseInt(this.$wrapperEl.css('left'));
        },
        setTranslate(translate) {
          if (!translate) {
            return;
          }

          const $w = this.$wrapperEl;
          const max = $w[0].offsetWidth - $w.find('.q-table__container')[0].offsetWidth;
          const left = Math.max(max, Math.min(0, (this.LEFT || 0) + translate));

          $w.css('left', `${left}px`);
        },
      },
    },
  }),
  created() {
    const N = 30;

    this.columns =  [
      { name:         'id', label:          'Id', align: 'right', type: 'number' },
      { name:      'descr', label: 'Description', align:  'left', type:   'text' },
      { name: 'start_date', label:  'Start date', align:  'left', type:   'date' },
      ...[...Array(N)].map((n, i) => ({
        name: `notes${i}`,
        label: `Notes${i}`,
        align: 'left',
        type: 'text',
      })),
    ];

    this.items = [
      { descr: 'Plumbing', start_date: '2019-03-09', notes: 'Libero non voluptas est' },
      { descr: 'Electric', start_date: '2019-04-19', notes: 'Vel exercitationem nam cumque', },
      { descr:  'Freight', start_date: '2019-05-17', notes: 'Quisquam molestiae in dicta dolores' },
    ].map((n, i) => ({
      ...n,
      id: i + 1,
      ...[...Array(N)].reduce((acc, n, i) => (acc[`notes${i}`] = `something${i}`, acc), {}),
    }));
  },
});