Files
ficheagent/resources/js/Components/App/ServiceTaskCard.vue

189 lines
8.6 KiB
Vue

<script setup>
import StatusBadge from '@/Components/App/StatusBadge.vue';
import { computed, reactive } from 'vue';
import { router, usePage } from '@inertiajs/vue3';
import CommentSection from '@/Components/App/CommentSection.vue';
import AttachmentSection from '@/Components/App/AttachmentSection.vue';
import TextInput from '@/Components/TextInput.vue';
import InputLabel from '@/Components/InputLabel.vue';
const props = defineProps({
task: {
type: Object,
required: true,
},
});
const user = computed(() => usePage().props.auth.user);
const canManage = computed(() => {
return user.value.roles.some(r => r.name === props.task.service.name || r.name === 'Admin');
});
const progress = computed(() => {
if (!props.task.task_items.length) return 0;
const completed = props.task.task_items.filter(i => i.is_completed).length;
return Math.round((completed / props.task.task_items.length) * 100);
});
// Initialize form state for custom fields
const fieldsState = reactive({});
props.task.task_items.forEach(item => {
fieldsState[item.id] = {};
if (item.fields_definition) {
item.fields_definition.forEach(field => {
// Load existing data or empty string
// We assume field.label is the key.
// Check if item.data exists and has this key.
const existingValue = item.data && item.data[field.label] ? item.data[field.label] : '';
fieldsState[item.id][field.label] = existingValue;
});
}
});
const startTask = () => {
router.post(route('service-tasks.start', props.task.id));
};
const toggleItem = (item, event) => {
// If we are marking as complete
if (!item.is_completed) {
// Validate required fields
if (item.fields_definition) {
for (const field of item.fields_definition) {
if (field.required && !fieldsState[item.id][field.label]) {
event.preventDefault();
alert(`Le champ "${field.label}" est obligatoire.`);
return;
}
}
}
}
const data = fieldsState[item.id] || {};
router.post(route('task-items.toggle', item.id), { data }, {
preserveScroll: true,
onFinish: () => {
// Optional: feedback
}
});
};
const validateTask = () => {
if (confirm('Souhaitez-vous valider cette tâche ?')) {
router.post(route('service-tasks.approve', props.task.id));
}
};
const rejectTask = () => {
const reason = prompt('Raison du refus :');
if (reason) {
router.post(route('service-tasks.reject', props.task.id), { reason });
}
};
</script>
<template>
<div class="overflow-hidden bg-white shadow sm:rounded-lg dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
<div class="px-4 py-5 sm:px-6 flex justify-between items-center">
<div>
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">
{{ task.service.name }}
</h3>
<p class="mt-1 max-w-2xl text-sm text-gray-500 dark:text-gray-400">
Deadline: {{ task.sla_deadline ? new Date(task.sla_deadline).toLocaleString() : 'N/A' }}
</p>
</div>
<div class="flex items-center space-x-2">
<button
v-if="canManage && task.status === 'pending'"
@click="startTask"
class="px-3 py-1 bg-blue-600 text-white rounded text-xs font-bold hover:bg-blue-700"
>
Démarrer
</button>
<div v-if="canManage && task.status === 'waiting_validation'" class="flex space-x-2">
<button @click="validateTask" class="px-3 py-1 bg-green-600 text-white rounded text-xs font-bold hover:bg-green-700">Valider</button>
<button @click="rejectTask" class="px-3 py-1 bg-red-600 text-white rounded text-xs font-bold hover:bg-red-700">Refuser</button>
</div>
<StatusBadge :status="task.status" />
</div>
</div>
<div class="px-4 py-3 bg-gray-50 dark:bg-gray-700/50">
<div class="w-full bg-gray-200 dark:bg-gray-600 rounded-full h-1.5 mb-2">
<div class="bg-blue-600 h-1.5 rounded-full transition-all duration-500" :style="{ width: progress + '%' }"></div>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 text-right">{{ progress }}% complété</p>
</div>
<div class="border-t border-gray-200 dark:border-gray-700 px-4 py-3 sm:p-0">
<dl class="divide-y divide-gray-200 dark:divide-gray-700">
<div v-for="item in task.task_items" :key="item.id" class="py-4 px-6">
<div class="flex items-center justify-between">
<div class="flex items-center w-full">
<input
type="checkbox"
:checked="item.is_completed"
:disabled="!canManage || (task.status !== 'in_progress' && task.status !== 'waiting_validation')"
@click="toggleItem(item, $event)"
class="h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500 dark:bg-gray-900 dark:border-gray-600 cursor-pointer disabled:cursor-not-allowed shrink-0"
>
<div class="ml-3 flex flex-col w-full">
<span :class="['text-sm font-medium', item.is_completed ? 'line-through text-gray-400' : 'text-gray-900 dark:text-gray-100']">
{{ item.label }}
</span>
</div>
</div>
<span v-if="item.is_mandatory" class="ml-2 text-[10px] uppercase font-bold text-red-500 dark:text-red-400 shrink-0">Obligatoire</span>
</div>
<!-- Custom Fields Section -->
<div v-if="item.fields_definition && item.fields_definition.length > 0" class="mt-3 ml-7 grid grid-cols-1 gap-y-3 gap-x-4 sm:grid-cols-2 bg-gray-50 dark:bg-gray-700/30 p-3 rounded-md">
<div v-for="(field, index) in item.fields_definition" :key="index">
<InputLabel :value="field.label + (field.required ? ' *' : '')" class="text-xs mb-1" />
<TextInput
v-if="field.type === 'text'"
type="text"
v-model="fieldsState[item.id][field.label]"
:disabled="item.is_completed || !canManage"
class="w-full text-xs py-1"
/>
<input
v-else-if="field.type === 'date'"
type="date"
v-model="fieldsState[item.id][field.label]"
:disabled="item.is_completed || !canManage"
class="block w-full border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-indigo-500 dark:focus:ring-indigo-600 rounded-md shadow-sm text-xs py-1"
/>
<div v-else-if="field.type === 'checkbox'" class="flex items-center mt-2">
<input
type="checkbox"
v-model="fieldsState[item.id][field.label]"
:disabled="item.is_completed || !canManage"
class="h-4 w-4 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500 dark:border-gray-700 dark:bg-gray-900"
/>
<span class="ml-2 text-xs text-gray-600 dark:text-gray-400">{{ field.label }}</span>
</div>
</div>
</div>
</div>
</dl>
</div>
<AttachmentSection
:attachments="task.attachments"
:task-id="task.id"
:can-manage="canManage"
class="px-4 pb-2"
/>
<CommentSection
:comments="task.comments"
:commentable-id="task.id"
commentable-type="App\Models\ServiceTask"
class="p-4"
/>
</div>
</template>