Initial commit: Ressourcenplanung-App

Vue 3 + Vite Frontend und Node/Express + SQLite Backend für
Projekt-, Aufgaben-, Mitarbeiter- und Teamverwaltung inkl.
Gantt-Zeitplan und Auslastungs-Heatmap.
This commit is contained in:
2026-07-03 22:45:07 +02:00
commit fc25eff643
24 changed files with 3938 additions and 0 deletions
+123
View File
@@ -0,0 +1,123 @@
<script setup>
import { reactive } from 'vue';
import { store } from './store.js';
import { formatDate } from './helpers.js';
const newProject = reactive({ name: '', description: '', start_date: '', end_date: '', color: '#4f46e5' });
const newTask = reactive({});
const newAssignment = reactive({});
const expanded = reactive({});
async function addProject() {
if (!newProject.name) return;
await store.createProject({ ...newProject });
Object.assign(newProject, { name: '', description: '', start_date: '', end_date: '', color: '#4f46e5' });
}
function taskForm(projectId) {
if (!newTask[projectId]) newTask[projectId] = { name: '', start_date: '', end_date: '', estimated_hours: 0, status: 'geplant' };
return newTask[projectId];
}
async function addTask(projectId) {
const form = taskForm(projectId);
if (!form.name || !form.start_date || !form.end_date) return;
await store.createTask(projectId, { ...form });
Object.assign(form, { name: '', start_date: '', end_date: '', estimated_hours: 0, status: 'geplant' });
}
function assignmentForm(taskId) {
if (!newAssignment[taskId]) newAssignment[taskId] = { employee_id: '', allocated_hours_per_week: 8 };
return newAssignment[taskId];
}
async function addAssignment(projectId, taskId) {
const form = assignmentForm(taskId);
if (!form.employee_id) return;
await store.assignEmployee(projectId, taskId, Number(form.employee_id), Number(form.allocated_hours_per_week));
Object.assign(form, { employee_id: '', allocated_hours_per_week: 8 });
}
</script>
<template>
<div class="panel-section">
<h2>Neues Projekt</h2>
<div class="form-row">
<input v-model="newProject.name" placeholder="Projektname" />
<input v-model="newProject.description" placeholder="Beschreibung" />
<input v-model="newProject.start_date" type="date" />
<input v-model="newProject.end_date" type="date" />
<input v-model="newProject.color" type="color" />
<button class="btn" @click="addProject">Anlegen</button>
</div>
</div>
<div class="panel-section" v-for="project in store.projects" :key="project.id">
<div style="display: flex; justify-content: space-between; align-items: center">
<h2 :style="{ color: project.color }">{{ project.name }}</h2>
<button class="btn secondary" @click="expanded[project.id] = !expanded[project.id]">
{{ expanded[project.id] ? 'Zuklappen' : 'Aufgaben verwalten' }}
</button>
</div>
<p class="muted">{{ project.description }} · {{ formatDate(project.start_date) }} {{ formatDate(project.end_date) }}</p>
<table>
<thead>
<tr>
<th>Aufgabe</th>
<th>Start</th>
<th>Ende</th>
<th>Stunden</th>
<th>Status</th>
<th>Zugewiesen</th>
</tr>
</thead>
<tbody>
<tr v-for="task in project.tasks" :key="task.id">
<td>{{ task.name }}</td>
<td>{{ formatDate(task.start_date) }}</td>
<td>{{ formatDate(task.end_date) }}</td>
<td>{{ task.estimated_hours }}</td>
<td>{{ task.status }}</td>
<td>
<span v-for="a in task.assignments" :key="a.id" class="tag">
{{ a.employee_name }} ({{ a.allocated_hours_per_week }}h)
</span>
</td>
</tr>
</tbody>
</table>
<template v-if="expanded[project.id]">
<h3>Neue Aufgabe</h3>
<div class="form-row">
<input v-model="taskForm(project.id).name" placeholder="Aufgabenname" />
<input v-model="taskForm(project.id).start_date" type="date" />
<input v-model="taskForm(project.id).end_date" type="date" />
<input v-model.number="taskForm(project.id).estimated_hours" type="number" placeholder="Stunden" style="width: 90px" />
<select v-model="taskForm(project.id).status">
<option value="geplant">geplant</option>
<option value="in Arbeit">in Arbeit</option>
<option value="erledigt">erledigt</option>
</select>
<button class="btn" @click="addTask(project.id)">Hinzufügen</button>
</div>
<div v-for="task in project.tasks" :key="'assign-' + task.id" class="form-row">
<span style="width: 180px">{{ task.name }}:</span>
<select v-model="assignmentForm(task.id).employee_id">
<option value="" disabled>Mitarbeiter wählen</option>
<option v-for="e in store.employees" :key="e.id" :value="e.id">{{ e.name }}</option>
</select>
<input v-model.number="assignmentForm(task.id).allocated_hours_per_week" type="number" style="width: 90px" placeholder="Std/Woche" />
<button class="btn secondary" @click="addAssignment(project.id, task.id)">Zuweisen</button>
<button class="btn danger" @click="store.deleteTask(project.id, task.id)">Aufgabe löschen</button>
</div>
</template>
<div style="margin-top: 12px">
<button class="btn danger" @click="store.deleteProject(project.id)">Projekt löschen</button>
</div>
</div>
<p v-if="store.projects.length === 0" class="muted">Noch keine Projekte angelegt.</p>
</template>