@@ -56,11 +56,13 @@ class ProjectsController | |||
public function saveTask(Request $request, Response $response, array $args) | |||
{ | |||
$db = $this->container->get('db'); | |||
$query = $db->prepare('UPDATE task SET name = :name, description = :description WHERE id = :id'); | |||
$query = $db->prepare('UPDATE task SET name = :name, description = :description, assignee_id = :userId, time = :time WHERE id = :id'); | |||
$query->bindValue(':name', $_POST['name']); | |||
$query->bindValue(':description', $_POST['description']); | |||
$query->bindValue(':id', $_POST['id']); | |||
$query->bindValue(':userId', $_POST['userId']); | |||
$query->bindValue(':time', $_POST['time']); | |||
if ($query->execute()) { | |||
$response->getBody()->write(strval($db->lastInsertRowID())); | |||
@@ -103,9 +105,10 @@ class ProjectsController | |||
$db = $this->container->get('db'); | |||
$query = $db->prepare(' | |||
SELECT T.name, T.description, T.state_id, T.id, T.row | |||
SELECT T.name, T.description, T.state_id, T.assignee_id, E.user_name, T.id, T.row, T.time | |||
FROM task T | |||
JOIN project P on T.project_id = P.id | |||
JOIN employee E on T.assignee_id = E.id | |||
WHERE T.project_id = :id | |||
ORDER BY row | |||
'); | |||
@@ -137,7 +140,7 @@ class ProjectsController | |||
{ | |||
$db = $this->container->get('db'); | |||
$query = $db->prepare(' | |||
SELECT P.id, P.name, P.description, P.team_id, T.name as team_name, P.client_id, C.name as client_name | |||
SELECT P.id, P.name, P.description, P.team_id, P.city, P.country, T.name as team_name, P.client_id, C.name as client_name, C.btw_nr, C.address | |||
FROM project P | |||
LEFT JOIN team T | |||
ON P.team_id = T.id |
@@ -17,9 +17,10 @@ class Task | |||
{ | |||
$db = $this->container->get('db'); | |||
$query = $db->prepare(' | |||
SELECT T.name, T.description, T.state_id, T.id, T.row | |||
SELECT T.name, T.description, T.state_id, E.user_name, T.id, T.row, T.time | |||
FROM task T | |||
JOIN project P on T.project_id = p.id | |||
JOIN employee E on T.assignee_id = E.id | |||
WHERE T.project_id = 1 | |||
ORDER BY row | |||
'); |
@@ -8,7 +8,6 @@ | |||
<li class="test"><nuxt-link to="/kanban">Kanban</nuxt-link></li> | |||
<li class="test"><nuxt-link to="/projects">Projects</nuxt-link></li> | |||
<li class="test"><nuxt-link to="/teams">Teams</nuxt-link></li> | |||
<li class="test"><nuxt-link to="/factuur">Facturatie</nuxt-link></li> | |||
<li class="test"> | |||
<nuxt-link to="/clients">Bedrijfsgegevens</nuxt-link> | |||
</li> |
@@ -1,92 +0,0 @@ | |||
<template> | |||
<div class="container"> | |||
<div class="flex-container"> | |||
<div> | |||
<input v-model="search" type="text" placeholder="projects" /> | |||
</div> | |||
<div> | |||
<table> | |||
<thead> | |||
<tr> | |||
<th> | |||
name | |||
</th> | |||
<th> | |||
description | |||
</th> | |||
</tr> | |||
</thead> | |||
<tbody v-if="!loading && !search"> | |||
<tr v-for="project in projects" :key="project.id"> | |||
<td>{{ project.name }}</td> | |||
<td>{{ project.description }}</td> | |||
<button>Maak factuur</button> | |||
</tr> | |||
</tbody> | |||
<tbody v-if="!loading && search"> | |||
<tr v-for="project in filteredProjects" :key="project.id"> | |||
<td>{{ project.name }}</td> | |||
<td>{{ project.description }}</td> | |||
<button>Maak factuur</button> | |||
</tr> | |||
</tbody> | |||
<p v-if="loading">loading...</p> | |||
</table> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import { mapState, mapActions } from 'vuex'; | |||
export default { | |||
data() { | |||
return { | |||
filterTimeout: null, | |||
search: '', | |||
filteredProjects: [], | |||
}; | |||
}, | |||
layout: 'nav&foot', | |||
computed: { | |||
...mapState({ projects: (state) => state.projects.list }), | |||
}, | |||
watch: { | |||
search(val) { | |||
this.filteredProjects = this.projects.filter( | |||
(project) => | |||
project.description.toLowerCase().includes(val.toLowerCase()) || | |||
project.name.toLowerCase().includes(val.toLowerCase()), | |||
); | |||
}, | |||
}, | |||
// ↓ put it in for reference ↓ | |||
/* watch: { | |||
$route: { | |||
immediate: true, | |||
handler(route) { | |||
this.search = route.query.name; | |||
console.log('test'); | |||
this.pullChar({ pageNumber: this.pageNumber, name: this.search }); | |||
// this.pullChar should be this.pullProjects | |||
}, | |||
}, | |||
}, */ | |||
mounted() { | |||
if (!this.hasProjects) this.fetchAllProjects(); | |||
}, | |||
methods: { | |||
...mapActions({ fetchAllProjects: 'projects/fetchAllProjects' }), | |||
}, | |||
// ↓ put it in for reference ↓ | |||
/* toSearch() { | |||
clearTimeout(this.filterTimeout); | |||
this.filterTimeout = setTimeout(() => { | |||
this.$router.push({ | |||
path: '/facturatie/1', | |||
query: { name: document.getElementById('filterInp').value }, | |||
}); | |||
}, 1000); | |||
}, */ | |||
}; | |||
</script> |
@@ -58,34 +58,48 @@ | |||
</div> | |||
<div class="info-container"> | |||
<div>test</div> | |||
<input id="text" type="text" placeholder="users" /> | |||
<input | |||
id="assign" | |||
v-model="user.user_name" | |||
type="text" | |||
placeholder="assign user" | |||
autocomplete="off" | |||
@focus="modal = true" | |||
/> | |||
<div v-if="filteredUser && modal"> | |||
<ul class="filter"> | |||
<li | |||
v-for="filteredUser in filteredUsers" | |||
:key="filteredUser.id" | |||
@click="setUser(filteredUser)" | |||
> | |||
{{ filteredUser.user_name }} | |||
</li> | |||
</ul> | |||
</div> | |||
<textarea | |||
id="textarea" | |||
name="" | |||
cols="30" | |||
rows="10" | |||
placeholder="data" | |||
></textarea> | |||
<button class="button" @click="saveTask">Save</button> | |||
<FormulateForm @submit="saveTask"> | |||
<FormulateInput | |||
id="text" | |||
v-model="selectedTask.name" | |||
type="text" | |||
placeholder="Title" | |||
/> | |||
<FormulateInput | |||
id="assign" | |||
v-model="selectedTask.user_name" | |||
type="text" | |||
placeholder="assign user" | |||
autocomplete="off" | |||
@focus="modal = true" | |||
/> | |||
<div> | |||
<ul class="filter"> | |||
<li | |||
v-for="editTeam in editTeams" | |||
:key="editTeam.id" | |||
@click="setUser(editTeam)" | |||
> | |||
{{ editTeam.user_name }} <br /> | |||
</li> | |||
</ul> | |||
</div> | |||
<FormulateInput | |||
id="textarea" | |||
v-model="selectedTask.description" | |||
type="textarea" | |||
name="" | |||
cols="30" | |||
rows="10" | |||
placeholder="data" | |||
/> | |||
<FormulateInput | |||
v-model="selectedTask.time" | |||
type="number" | |||
placeholder="Worked hrs." | |||
/> | |||
<FormulateInput type="submit" value="save" label="Save" /> | |||
</FormulateForm> | |||
<button class="button" @click="deleteTask">Delete</button> | |||
</div> | |||
</div> | |||
@@ -126,7 +140,7 @@ export default { | |||
user: { id: 1, user_name: '' }, | |||
users: [], | |||
filteredUsers: [], | |||
editTeam: [], | |||
editTeams: [], | |||
}; | |||
}, | |||
computed: { | |||
@@ -175,7 +189,8 @@ export default { | |||
}, | |||
setUser(user) { | |||
this.user = JSON.parse(JSON.stringify(user)); | |||
this.editTeam.push(user); | |||
console.log(user); | |||
this.selectedTask.user_name = user.user_name; | |||
this.modal = false; | |||
}, | |||
newTask() { | |||
@@ -243,22 +258,23 @@ export default { | |||
// console.log(arguments); | |||
}, | |||
getTaskData(taskItem, e, task) { | |||
const text = document.getElementById('text'); | |||
const textArea = document.getElementById('textarea'); | |||
// get data from card | |||
this.selectedTask = taskItem; | |||
this.selectedTask = JSON.parse(JSON.stringify(taskItem)); | |||
console.log(this.selectedTask); | |||
// set data from card | |||
text.value = this.selectedTask.name; | |||
textArea.value = this.selectedTask.description; | |||
}, | |||
saveTask() { | |||
const text = document.getElementById('text'); | |||
const textArea = document.getElementById('textarea'); | |||
let employeeId; | |||
this.teamMembers.forEach((element) => { | |||
if (element.user_name === this.selectedTask.user_name) { | |||
employeeId = element.employee_id; | |||
} | |||
}); | |||
const taskData = new URLSearchParams(); | |||
taskData.append('name', text.value); | |||
taskData.append('description', textArea.value); | |||
taskData.append('name', this.selectedTask.name); | |||
taskData.append('description', this.selectedTask.description); | |||
taskData.append('id', this.selectedTask.id); | |||
taskData.append('userId', employeeId); | |||
taskData.append('time', this.selectedTask.time); | |||
this.$axios | |||
.post('/saveTask', taskData) | |||
.then(() => { | |||
@@ -276,13 +292,14 @@ export default { | |||
(obj) => obj.id === Number(this.projectId), | |||
).name; | |||
this.fetchMergeStates({ id: this.projectId }); | |||
this.teamMembers = []; | |||
this.editTeam = []; | |||
console.log(this.teamMembers); | |||
this.editTeams = []; | |||
this.teamMembers.forEach((element) => { | |||
if (element.team_id === 5) { | |||
this.editTeam.push(element); | |||
if (element.team_id === Number(this.projectId)) { | |||
this.editTeams.push(element); | |||
} | |||
}); | |||
console.log(this.editTeams); | |||
}, | |||
deleteTask() { | |||
const taskData = new URLSearchParams(); | |||
@@ -313,6 +330,14 @@ export default { | |||
// }); | |||
// }, | |||
}, | |||
watch: { | |||
user: { | |||
deep: true, | |||
handler() { | |||
this.filteredU(); | |||
}, | |||
}, | |||
}, | |||
}; | |||
</script> | |||
@@ -528,4 +553,8 @@ textarea { | |||
font-size: 16px; | |||
border-radius: 0.25rem; | |||
} | |||
.timeField { | |||
height: 50px; | |||
} | |||
</style> |
@@ -27,6 +27,11 @@ | |||
meer info | |||
</n-link> | |||
</td> | |||
<td> | |||
<n-link :to="'/projects/invoice/' + project.id"> | |||
make invoice | |||
</n-link> | |||
</td> | |||
</tr> | |||
</tbody> | |||
<tbody v-if="!loading && search"> |
@@ -0,0 +1,227 @@ | |||
<template> | |||
<div class="invoice-container"> | |||
<div class="invoice-header"> | |||
<div class="invoice-header-items"> | |||
<h2>Invoice</h2> | |||
<div> | |||
<div> | |||
<h2>{{ project.name }}</h2> | |||
</div> | |||
<div>{{ project.team_name }}</div> | |||
<div>{{ project.city }}</div> | |||
<div>{{ project.country }}</div> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="invoice"> | |||
<div class="invoice-top"> | |||
<div> | |||
<h5>BILL TO:</h5> | |||
<div>{{ project.client_name }}</div> | |||
<div>{{ project.btw_nr }}</div> | |||
<div>{{ project.address }}</div> | |||
</div> | |||
<div> | |||
<h5>invoice #</h5> | |||
<div>{{ InvoiceNumber }}</div> | |||
<h5>Date</h5> | |||
<div>{{ Today }}</div> | |||
<h5>Invoie due date</h5> | |||
<div>{{ InvoiceDueDate }}</div> | |||
</div> | |||
</div> | |||
<div class="invoice-bottom"> | |||
<table class="invoice-table"> | |||
<thead> | |||
<tr> | |||
<th> | |||
TASKS | |||
</th> | |||
<th> | |||
DESCRPTION | |||
</th> | |||
<th> | |||
HRS | |||
</th> | |||
<th> | |||
PRICE | |||
</th> | |||
<th> | |||
TAX | |||
</th> | |||
<th> | |||
AMOUNT | |||
</th> | |||
</tr> | |||
</thead> | |||
<tbody v-for="state in mergeStates" :key="state.id"> | |||
<tr v-for="Taskitem in state.tasks" :key="Taskitem.id"> | |||
<td>{{ Taskitem.name }}</td> | |||
<td>{{ Taskitem.description }}</td> | |||
<td>{{ Taskitem.time }}</td> | |||
<td>50</td> | |||
<td>20%</td> | |||
<td> | |||
{{ ((50 * Taskitem.time) / 100) * 20 + 50 * Taskitem.time }} | |||
</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
</div> | |||
</div> | |||
<div class="invoice-footer"> | |||
<div class="invoice-footer-left"> | |||
<h5>Extra:</h5> | |||
<div class="invoice-footer-description">{{ project.description }}</div> | |||
</div> | |||
<div class="invoice-footer-right"> | |||
<div>Total</div> | |||
<div>€{{ Total }}</div> | |||
</div> | |||
</div> | |||
<Loader /> | |||
</div> | |||
</template> | |||
<script> | |||
import { mapState, mapActions } from 'vuex'; | |||
import Loader from '~/components/Loader'; | |||
export default { | |||
components: { Loader }, | |||
data() { | |||
return { | |||
id: Number(this.$route.params.id), | |||
AllTasks: [], | |||
GetAllTasks: [], | |||
Total: 0, | |||
InvoiceNumber: '', | |||
Today: '', | |||
InvoiceDueDate: '', | |||
}; | |||
}, | |||
layout: 'nav&foot', | |||
computed: { | |||
...mapState({ | |||
project: (state) => state.projects.currentProject, | |||
loading: 'loading', | |||
}), | |||
...mapState({ | |||
projects: (state) => state.mergeStates, | |||
mergeStates: 'mergeStates', | |||
loading: 'loading', | |||
}), | |||
}, | |||
created() {}, | |||
mounted() { | |||
this.id = Number(this.$route.params.id); | |||
this.fetchMergeStates({ id: this.id }); | |||
console.log(this.id); | |||
this.fetchProject(this.id); | |||
this.CreateInvoiceNumber(); | |||
this.GetDate(); | |||
this.Calculate(); | |||
}, | |||
methods: { | |||
...mapActions({ | |||
fetchProject: 'projects/fetchProject', | |||
saveProject: 'projects/saveProject', | |||
}), | |||
...mapActions(['fetchMergeStates']), | |||
Calculate() { | |||
this.fetchMergeStates({ id: this.id }); | |||
this.Total = 0; | |||
console.log(this.mergeStates); | |||
this.mergeStates.forEach((state) => { | |||
state.tasks.forEach((Taskitem) => { | |||
this.Total += ((50 * Taskitem.time) / 100) * 20 + 50 * Taskitem.time; | |||
}); | |||
}); | |||
console.log(this.Total); | |||
}, | |||
CreateInvoiceNumber() { | |||
this.InvoiceNumber = this.id.toString().padStart(5, '0'); | |||
}, | |||
GetDate() { | |||
const today = new Date(); | |||
let date = today.toLocaleDateString(); | |||
this.Today = date; | |||
today.setMonth(today.getMonth() + 2); | |||
date = today.toLocaleDateString(); | |||
this.InvoiceDueDate = date; | |||
}, | |||
}, | |||
}; | |||
</script> | |||
<style> | |||
.invoice-container { | |||
flex-direction: column; | |||
display: flex; | |||
} | |||
.invoice-header { | |||
height: 200px; | |||
width: 100%; | |||
background-color: #2065a8; | |||
display: flex; | |||
} | |||
.invoice-header-items { | |||
align-self: center; | |||
width: 100%; | |||
justify-content: space-between; | |||
padding: 50px; | |||
display: flex; | |||
color: white; | |||
} | |||
.invoice-footer { | |||
display: flex; | |||
height: 150px; | |||
width: 100%; | |||
flex-direction: row; | |||
} | |||
.invoice-footer-left { | |||
background-color: #bcdff5; | |||
width: 65%; | |||
display: block; | |||
padding-top: 50px; | |||
padding-left: 20px; | |||
} | |||
.invoice-footer-left.invoice-footer-description { | |||
width: 150px; | |||
overflow: hidden; | |||
height: 1em; | |||
} | |||
.invoice-footer-right { | |||
background-color: #2065a8; | |||
width: 35%; | |||
font-size: 50px; | |||
color: white; | |||
padding: 15px; | |||
float: left; | |||
} | |||
.invoice { | |||
display: flex; | |||
flex-direction: column; | |||
height: auto; | |||
} | |||
.invoice-top { | |||
display: flex; | |||
flex-direction: row; | |||
justify-content: space-between; | |||
padding: 50px; | |||
height: 35%; | |||
border-bottom: 1px solid black; | |||
width: 100%; | |||
} | |||
.invoice-bottom { | |||
padding: 50px; | |||
} | |||
.invoice-table { | |||
width: 100%; | |||
} | |||
.invoice-table td, | |||
.invoice-table th { | |||
padding: 15px; | |||
text-align: left; | |||
} | |||
</style> |
@@ -69,6 +69,7 @@ export const actions = { | |||
.catch((error) => console.error(error)); | |||
}, | |||
fetchMergeStates({ commit }, { id }) { | |||
commit('setLoading', true, { root: true }); | |||
const params = { | |||
project_id: id, | |||
}; | |||
@@ -76,6 +77,7 @@ export const actions = { | |||
.get('/mergeStates', { params }) | |||
.then((res) => { | |||
commit('setMergeStates', res.data); | |||
commit('setLoading', false, { root: true }); | |||
}) | |||
.catch((error) => console.error(error)); | |||
}, |