@@ -1,6 +1,6 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
export default axios.create({
|
export default axios.create({
|
||||||
baseURL: 'https://88c09a70-ab85-412a-9bd3-407afe8986a1.mock.pstmn.io/',
|
baseURL: 'https://5f672efd-7bb4-4147-b5f7-64d3fa1a3873.mock.pstmn.io/',
|
||||||
timeout: 4000
|
timeout: 4000
|
||||||
})
|
})
|
||||||
|
|||||||
42
src/components/tabs/ClientTab.vue
Normal file
42
src/components/tabs/ClientTab.vue
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<template>
|
||||||
|
<div class="bg-zinc-50">
|
||||||
|
<div class="py-2">
|
||||||
|
<table class="w-full table-auto">
|
||||||
|
<tr class="border-b">
|
||||||
|
<td class="text align-top text-xl pl-4 max-sm:text-base py-2">Name:</td>
|
||||||
|
<td class="w-full pl-3 text-lg max-sm:text-base py-2">{{ contract.client.name }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="pt-5 border-b">
|
||||||
|
<td class="text align-top text-xl pl-4 max-sm:text-base py-2">Address:</td>
|
||||||
|
<td class="w-full pl-3 text-lg max-sm:text-base py-2">{{ contract.client.address }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="border-b">
|
||||||
|
<td class="text align-top text-xl pl-4 max-sm:text-base py-2">Anschprechpartner:</td>
|
||||||
|
<td class="w-full pl-3 text-lg max-sm:text-base py-2">{{ contract.client.contactPerson }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="border-b">
|
||||||
|
<td class="text align-top text-xl pl-4 max-sm:text-base py-2">Email:</td>
|
||||||
|
<td class="w-full pl-3 text-lg max-sm:text-base py-2">
|
||||||
|
<a v-bind:href="'mailto:' + contract.client.email">
|
||||||
|
<font-awesome-icon class="pl-1 h-5 mt-1" icon='fa-solid fa-envelope'/>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="border-b">
|
||||||
|
<td class="text align-top text-xl pl-4 max-sm:text-base py-2">Nummer:</td>
|
||||||
|
<td class="w-full pl-3 text-lg max-sm:text-base py-2">{{ contract.client.phone }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
contract: {
|
||||||
|
type: Object
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
53
src/components/tabs/CommentsTab.vue
Normal file
53
src/components/tabs/CommentsTab.vue
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<template>
|
||||||
|
<div class="bg-zinc-50">
|
||||||
|
<div class="flex flex-row justify-center py-5">
|
||||||
|
<ul class="justify-around">
|
||||||
|
<li v-for="comment in contract.comments" class="bg-white rounded-lg shadow-2xl mb-4">
|
||||||
|
<div class="relative grid grid-cols-1 gap-4 p-4 mb-8 border rounded-lg bg-white shadow-lg">
|
||||||
|
<div class="relative flex gap-4">
|
||||||
|
<font-awesome-icon icon="fa-solid fa-user-large" class="relative -mb-4 bg-white h-11 w-11" />
|
||||||
|
<div class="flex flex-col w-full">
|
||||||
|
<div class="flex flex-row justify-between">
|
||||||
|
<p class="relative text-xl whitespace-nowrap truncate overflow-hidden">{{comment.name}}</p>
|
||||||
|
<a class="text-gray-500 text-xl" href="#"><i class="fa-solid fa-trash"></i></a>
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-400 text-sm">{{new Date(comment.date).toDateString() }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="-mt-4 text-gray-500"> {{comment.message}} </p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-center">
|
||||||
|
|
||||||
|
<div class="py-4 mb-10 shadow-2xl">
|
||||||
|
<form class="w-full max-w-xl bg-white rounded-lg px-4 pt-2">
|
||||||
|
<div class="flex flex-wrap -mx-3 mb-6">
|
||||||
|
<h2 class="px-4 pt-3 pb-2 text-gray-800 text-lg">Add comment</h2>
|
||||||
|
<div class="w-full md:w-full px-3 mb-2 mt-2">
|
||||||
|
<textarea class="bg-gray-100 rounded border border-gray-400 leading-normal resize-none w-full h-20 py-2 px-3 font-medium placeholder-gray-700 focus:outline-none focus:bg-white" name="body" placeholder='Type Your Comment' required></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="w-full md:w-full flex items-start md:w-full px-3">
|
||||||
|
<div class="flex items-start w-1/2 text-gray-700 px-2 mr-auto"></div>
|
||||||
|
<div class="-mr-1">
|
||||||
|
<input type='submit' class="bg-white text-gray-700 font-medium py-1 px-4 border border-gray-400 rounded-lg tracking-wide mr-1 hover:bg-gray-100" value='Post Comment'>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
contract: {
|
||||||
|
type: Object
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
75
src/components/tabs/ContractTab.vue
Normal file
75
src/components/tabs/ContractTab.vue
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<template>
|
||||||
|
<div class="bg-zinc-50">
|
||||||
|
<div class="py-2">
|
||||||
|
<table class="w-full table-auto">
|
||||||
|
<tr class="border-b">
|
||||||
|
<td class="text align-top text-xl pl-4 max-sm:text-base py-2">ID:</td>
|
||||||
|
<td class="w-full pl-2 text-lg max-sm:text-base py-2">{{ contract.id }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="border-b">
|
||||||
|
<td class="text align-top text-xl pl-4 max-sm:text-base py-2">Name:</td>
|
||||||
|
<td class="w-full pl-2 text-lg max-sm:text-base py-2">{{ contract.name }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="pt-5 border-b">
|
||||||
|
<td class="text align-top text-xl pl-4 max-sm:text-base py-2">Status:</td>
|
||||||
|
<td class="w-full pl-2 text-lg max-sm:text-base py-2">
|
||||||
|
<span class="px-3 py-1 text-sm rounded-full bg-cyan-300 text-slate-800 bg-cyan-200 font-semibold">
|
||||||
|
{{ contract.status }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="border-b">
|
||||||
|
<td class="text align-top text-xl pl-4 max-sm:text-base py-2">Mitarbeiter:</td>
|
||||||
|
<td class="w-full pl-2 text-lg max-sm:text-base py-2">
|
||||||
|
<ul class="list-disc pl-4">
|
||||||
|
<li v-for="person in contract.mitarbeiter">
|
||||||
|
{{ person.name }}({{ person.role }}) {{person.phone}}
|
||||||
|
<a v-bind:href="'mailto:' + person.email">
|
||||||
|
<font-awesome-icon class="pl-1 h-5 mt-1" icon='fa-solid fa-envelope'/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="border-b">
|
||||||
|
<td class="text align-top text-xl pl-4 max-sm:text-base py-2">Unterlagen:</td>
|
||||||
|
<td class="w-full pl-2 text-lg max-sm:text-base py-2">
|
||||||
|
<ul class="list-disc pl-4">
|
||||||
|
<li v-for="document in contract.unterlagen">
|
||||||
|
<a v-bind:href='document.url' class="underline">
|
||||||
|
{{ document.name }}
|
||||||
|
<font-awesome-icon class="h-3 mb-0.5 pl-1" icon="fa-solid fa-arrow-up-right-from-square" />
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="border-b">
|
||||||
|
<td class="text align-top text-xl pl-4 max-sm:text-base py-2">Beschreibung:</td>
|
||||||
|
<td class="w-full pl-2 text-lg max-sm:text-base py-2">{{ contract.beschreibung }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text align-top text-xl pl-4 max-sm:text-base py-2">Beispiele:</td>
|
||||||
|
<td class="w-full pl-2 text-lg py-2">
|
||||||
|
<li v-for="(example, index) in contract.examples">
|
||||||
|
<a v-bind:href='example' class="underline">
|
||||||
|
Link {{ index + 1 }}
|
||||||
|
<font-awesome-icon class="h-3 mb-0.5 pl-1" icon="fa-solid fa-arrow-up-right-from-square" />
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
contract: {
|
||||||
|
type: Object
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
13
src/main.js
13
src/main.js
@@ -5,13 +5,24 @@ import { createI18n } from './i18n'
|
|||||||
import store from './store'
|
import store from './store'
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
||||||
import { faUserSecret, faPenToSquare } from '@fortawesome/free-solid-svg-icons'
|
import {
|
||||||
|
faUserSecret,
|
||||||
|
faPenToSquare,
|
||||||
|
faEnvelope,
|
||||||
|
faArrowUpRightFromSquare,
|
||||||
|
faCircleUser,
|
||||||
|
faUserLarge
|
||||||
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
import './assets/main.css'
|
import './assets/main.css'
|
||||||
|
|
||||||
/* add icons to the library */
|
/* add icons to the library */
|
||||||
library.add(faUserSecret)
|
library.add(faUserSecret)
|
||||||
library.add(faPenToSquare)
|
library.add(faPenToSquare)
|
||||||
|
library.add(faEnvelope)
|
||||||
|
library.add(faArrowUpRightFromSquare)
|
||||||
|
library.add(faCircleUser)
|
||||||
|
library.add(faUserLarge)
|
||||||
|
|
||||||
const i18n = createI18n()
|
const i18n = createI18n()
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,15 @@ const router = createRouter({
|
|||||||
requiresAuth: false
|
requiresAuth: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/contract',
|
||||||
|
name: 'contract',
|
||||||
|
// this page is lazy-loaded when the route is visited.
|
||||||
|
component: () => import('../views/ContractView.vue'),
|
||||||
|
meta: {
|
||||||
|
requiresAuth: false
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/forbidden',
|
path: '/forbidden',
|
||||||
name: 'forbidden',
|
name: 'forbidden',
|
||||||
|
|||||||
33
src/service/ContractsService.js
Normal file
33
src/service/ContractsService.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import HttpClient from '../api/HttpClient'
|
||||||
|
import router from '../router'
|
||||||
|
|
||||||
|
//Request in order to retrieve all contracts and print in table
|
||||||
|
//Backend: extra roles check. Allow admin, verwaltung, employee.
|
||||||
|
//REST: GET /contracts
|
||||||
|
//Auth: provide auth token in request
|
||||||
|
//OnError: redirect to page /error?message=somemessage&code=404
|
||||||
|
export async function getContracts() {
|
||||||
|
return HttpClient.get('contracts') //todo: provide here auth header
|
||||||
|
.then(resp => {
|
||||||
|
return resp.data.contracts
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
router.push('/error?message=' + error.message + '&code=' + error.code)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//Request in order to retrieve specific contract using ID
|
||||||
|
//Backend: extra roles check. Allow admin, verwaltung, employee.
|
||||||
|
//REST: GET /contract?id=someId
|
||||||
|
//Auth: provide auth token in request
|
||||||
|
//OnError: redirect to page /error?message=somemessage&code=404
|
||||||
|
export async function getContractById(identifier) {
|
||||||
|
return HttpClient.get('/contract', { params: { id: 'TESTID' } })
|
||||||
|
.then(resp => {
|
||||||
|
//todo: send also auth token with request
|
||||||
|
return resp.data
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
router.push('/error?message=' + error.message + '&code=' + error.code)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import HttpClient from '../api/HttpClient'
|
|
||||||
import router from '../router'
|
|
||||||
|
|
||||||
export async function getContracts() {
|
|
||||||
return HttpClient.get('contracts')
|
|
||||||
.then(resp => {
|
|
||||||
return resp.data.contracts
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
router.push('/error?message=' + error.message + '&code=' + error.code)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,20 +1,26 @@
|
|||||||
import { getContracts } from '../service/getContracts'
|
import { getContracts, getContractById } from '../service/ContractsService'
|
||||||
import HttpClient from '../api/HttpClient'
|
|
||||||
import router from '../router'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
contracts: []
|
contracts: [],
|
||||||
|
currentContract: null
|
||||||
}),
|
}),
|
||||||
mutations: {
|
mutations: {
|
||||||
initContracts(state, data) {
|
initContracts(state, data) {
|
||||||
state.contracts = data
|
state.contracts = data
|
||||||
|
},
|
||||||
|
setCurrentContract(state, data) {
|
||||||
|
state.currentContract = data
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async fetchContracts({ commit }) {
|
async fetchContracts({ commit }) {
|
||||||
const contracts = await getContracts()
|
const contracts = await getContracts()
|
||||||
commit('initContracts', contracts)
|
commit('initContracts', contracts)
|
||||||
|
},
|
||||||
|
async fetchContractById({ commit }, id) {
|
||||||
|
const currentContract = await getContractById(id)
|
||||||
|
commit('setCurrentContract', currentContract)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getters: {}
|
getters: {}
|
||||||
|
|||||||
90
src/views/ContractView.vue
Normal file
90
src/views/ContractView.vue
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
<template>
|
||||||
|
<Navbar />
|
||||||
|
<div class="flex justify-center py-5">
|
||||||
|
<p class="text-3xl"> {{ currentContract.name }} </p>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center text-center pt-5">
|
||||||
|
<ul class="flex border-b border-gray-200 text-center max-sm:overflow-x-scroll max-sm:overflow-y-hidden sm:w-2/3 ">
|
||||||
|
<li class="flex-1 cursor-pointer" @click="changeTab('contract')">
|
||||||
|
<p
|
||||||
|
v-bind:class="{
|
||||||
|
'block bg-gray-100 p-4 text-sm font-medium text-gray-500 ring-1 ring-inset ring-white': currentTab !== 'contract',
|
||||||
|
'relative block border-t border-l border-r border-gray-200 bg-white p-4 text-sm font-medium': currentTab === 'contract'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<span class="absolute inset-x-0 -bottom-px h-px w-full bg-white" v-if="currentTab === 'contract'"></span>
|
||||||
|
Auftrag
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="flex-1 pl-px cursor-pointer" @click="changeTab('client')">
|
||||||
|
<p
|
||||||
|
v-bind:class="{
|
||||||
|
'block bg-gray-100 p-4 text-sm font-medium text-gray-500 ring-1 ring-inset ring-white': currentTab !== 'client',
|
||||||
|
'relative block border-t border-l border-r border-gray-200 bg-white p-4 text-sm font-medium': currentTab === 'client'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
Auftraggeber
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="flex-1 cursor-pointer" @click="changeTab('comments')">
|
||||||
|
<p
|
||||||
|
v-bind:class="{
|
||||||
|
'block bg-gray-100 p-4 text-sm font-medium text-gray-500 ring-1 ring-inset ring-white': currentTab !== 'comments',
|
||||||
|
'relative block border-t border-l border-r border-gray-200 bg-white p-4 text-sm font-medium': currentTab === 'comments'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
Comments
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center text-left">
|
||||||
|
<div class="sm:w-2/3">
|
||||||
|
<ContractTab v-bind:contract="currentContract" v-if="currentTab === 'contract'"/>
|
||||||
|
<ClientTab v-bind:contract="currentContract" v-if="currentTab === 'client'"/>
|
||||||
|
<CommentsTab v-bind:contract="currentContract" v-if="currentTab === 'comments'"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Navbar from "../components/Navbar.vue";
|
||||||
|
import { mapActions } from "vuex";
|
||||||
|
import ClientTab from "../components/tabs/ClientTab.vue";
|
||||||
|
import ContractTab from "../components/tabs/ContractTab.vue";
|
||||||
|
import CommentsTab from "../components/tabs/CommentsTab.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
CommentsTab,
|
||||||
|
ContractTab,
|
||||||
|
ClientTab,
|
||||||
|
Navbar
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentTab: 'contract'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const id = this.$route.query.id
|
||||||
|
if (id === null){
|
||||||
|
this.$router.push('/error?message='+ 'Bad id' + '&code=404') //todo: check if works
|
||||||
|
}
|
||||||
|
this.fetchContractById(id);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(['fetchContractById']),
|
||||||
|
changeTab(tabName) {
|
||||||
|
this.currentTab = tabName
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentContract() {
|
||||||
|
return this.$store.state.contracts.currentContract
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -56,10 +56,12 @@ import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
|||||||
>
|
>
|
||||||
Update
|
Update
|
||||||
</button>
|
</button>
|
||||||
|
<RouterLink to="/contract?id=TESTID">
|
||||||
<button class="bg-emerald-500 text-white active:bg-emerald-600 font-bold uppercase text-xs px-4 py-2 rounded shadow hover:shadow-md outline-none focus:outline-none mr-1 mb-1 ease-linear transition-all duration-150" type="button"
|
<button class="bg-emerald-500 text-white active:bg-emerald-600 font-bold uppercase text-xs px-4 py-2 rounded shadow hover:shadow-md outline-none focus:outline-none mr-1 mb-1 ease-linear transition-all duration-150" type="button"
|
||||||
>
|
>
|
||||||
View
|
View
|
||||||
</button>
|
</button>
|
||||||
|
</RouterLink>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
Reference in New Issue
Block a user