167 lines
6.8 KiB
Vue
167 lines
6.8 KiB
Vue
<template>
|
|
<div class="container mx-auto p-4 animate-fade-in" v-if="project">
|
|
<!-- Header -->
|
|
<div class="glass-panel p-8 rounded-xl mb-8 relative overflow-hidden">
|
|
<div class="absolute top-0 right-0 p-4 opacity-10">
|
|
<i class="pi pi-folder text-9xl text-white"></i>
|
|
</div>
|
|
|
|
<div class="relative z-10">
|
|
<div class="flex items-center gap-3 mb-2">
|
|
<Button icon="pi pi-arrow-left" text rounded @click="router.push('/projects')" />
|
|
<span v-if="isCurrentProject"
|
|
class="bg-green-500/20 text-green-400 text-xs px-2 py-1 rounded-full border border-green-500/30 font-medium">
|
|
Active Project
|
|
</span>
|
|
</div>
|
|
|
|
<h1 class="text-4xl font-bold text-white mb-4">{{ project.name }}</h1>
|
|
<p class="text-slate-300 text-lg max-w-2xl mb-6">
|
|
{{ project.description || 'No description provided.' }}
|
|
</p>
|
|
|
|
<div class="flex gap-3">
|
|
<Button v-if="!isCurrentProject" label="Set as Active" icon="pi pi-check" @click="selectProject" />
|
|
<Button v-if="isOwner" label="Delete" icon="pi pi-trash" severity="danger" outlined
|
|
@click="confirmDelete" />
|
|
<Button label="Settings" icon="pi pi-cog" severity="secondary" outlined />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Confirm Dialog -->
|
|
<ConfirmDialog />
|
|
|
|
<!-- Content Grid -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
<!-- Members Column -->
|
|
<div class="lg:col-span-1">
|
|
<div class="glass-panel p-6 rounded-xl h-full">
|
|
<div class="flex justify-between items-center mb-6">
|
|
<h2 class="text-xl font-bold text-white">Team Members</h2>
|
|
</div>
|
|
<!-- Inline Add Member -->
|
|
<div v-if="isOwner" class="mb-6">
|
|
<div class="flex gap-2">
|
|
<InputText v-model="inviteUsername" placeholder="Username to add"
|
|
class="w-full p-inputtext-sm" @keyup.enter="addMember" />
|
|
<Button label="Add" icon="pi pi-user-plus" size="small" @click="addMember"
|
|
:loading="inviting" :disabled="!inviteUsername.trim()" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex flex-col gap-4">
|
|
<div v-for="memberId in project.members" :key="memberId"
|
|
class="flex items-center p-3 rounded-lg bg-slate-800/30 border border-slate-700/30">
|
|
<div
|
|
class="w-10 h-10 rounded-full bg-gradient-to-br from-indigo-500 to-purple-600 flex items-center justify-center text-white font-bold mr-3">
|
|
<i class="pi pi-user"></i>
|
|
</div>
|
|
<div class="overflow-hidden">
|
|
<p class="text-white font-medium truncate">{{ memberId === project.owner_id ? 'Owner' :
|
|
'Member' }}</p>
|
|
<p class="text-slate-500 text-xs truncate">ID: {{ memberId }}</p>
|
|
</div>
|
|
<div class="ml-auto" v-if="project.owner_id === memberId">
|
|
<i class="pi pi-crown text-yellow-500" title="Owner"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stats/Activity Column (Placeholder) -->
|
|
<div class="lg:col-span-2">
|
|
<div class="glass-panel p-6 rounded-xl h-full">
|
|
<h2 class="text-xl font-bold text-white mb-6">Activity</h2>
|
|
<div class="text-center py-12 text-slate-500">
|
|
<i class="pi pi-chart-line text-4xl mb-4 opacity-50"></i>
|
|
<p>No recent activity registered.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-else class="flex justify-center items-center h-full">
|
|
<div class="text-center">
|
|
<i class="pi pi-spin pi-spinner text-4xl text-primary-500 mb-4"></i>
|
|
<p class="text-slate-400">Loading project...</p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, onMounted } from 'vue';
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
import { useProjectsStore } from '@/stores/projectsStore';
|
|
import { storeToRefs } from 'pinia';
|
|
import { useAuthStore } from '@/stores/auth';
|
|
import Button from 'primevue/button';
|
|
import InputText from 'primevue/inputtext';
|
|
import ConfirmDialog from 'primevue/confirmdialog';
|
|
import { useConfirm } from 'primevue/useconfirm';
|
|
|
|
const route = useRoute();
|
|
const router = useRouter(); // Missing import fixed
|
|
const projectsStore = useProjectsStore();
|
|
const authStore = useAuthStore();
|
|
const { projects, currentProject } = storeToRefs(projectsStore);
|
|
|
|
const projectId = route.params.id;
|
|
const project = computed(() => projectsStore.getProjectById(projectId));
|
|
const isCurrentProject = computed(() => currentProject.value?.id === projectId);
|
|
const isOwner = computed(() => authStore.user && project.value && authStore.user.id === project.value.owner_id);
|
|
|
|
const inviteUsername = ref('');
|
|
const inviting = ref(false);
|
|
|
|
onMounted(async () => {
|
|
// Ensure projects are loaded
|
|
if (projects.value.length === 0) {
|
|
await projectsStore.fetchProjects();
|
|
}
|
|
});
|
|
|
|
const selectProject = () => {
|
|
if (project.value) {
|
|
projectsStore.selectProject(project.value.id);
|
|
}
|
|
}
|
|
|
|
const addMember = async () => {
|
|
if (!inviteUsername.value.trim()) return;
|
|
|
|
inviting.value = true;
|
|
try {
|
|
await projectsStore.addMember(projectId, inviteUsername.value);
|
|
inviteUsername.value = '';
|
|
} catch (e) {
|
|
console.error(e);
|
|
// Toast error here ideally
|
|
} finally {
|
|
inviting.value = false;
|
|
}
|
|
}
|
|
|
|
const confirm = useConfirm();
|
|
const confirmDelete = () => {
|
|
confirm.require({
|
|
message: 'Are you sure you want to delete this project? This action cannot be undone.',
|
|
header: 'Delete Confirmation',
|
|
icon: 'pi pi-exclamation-triangle',
|
|
acceptClass: 'p-button-danger',
|
|
acceptLabel: 'Delete',
|
|
rejectLabel: 'Cancel',
|
|
accept: async () => {
|
|
try {
|
|
await projectsStore.deleteProject(projectId);
|
|
router.push('/projects');
|
|
} catch (e) {
|
|
console.error("Failed to delete", e);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
</script>
|