863 lines
31 KiB
TypeScript
863 lines
31 KiB
TypeScript
|
|
import React, { useState } from "react";
|
|||
|
|
import {
|
|||
|
|
FaEye,
|
|||
|
|
FaEdit,
|
|||
|
|
FaPlus,
|
|||
|
|
FaTimes,
|
|||
|
|
FaSave,
|
|||
|
|
FaTrash,
|
|||
|
|
FaQuestionCircle,
|
|||
|
|
} from "react-icons/fa";
|
|||
|
|
import {
|
|||
|
|
HrEvaluation360Template,
|
|||
|
|
HrQuestionGroup,
|
|||
|
|
HrEvaluationQuestion,
|
|||
|
|
QuestionTypeEnum,
|
|||
|
|
HrQuestionOption,
|
|||
|
|
AssessorTypeEnum,
|
|||
|
|
} from "../../../types/hr";
|
|||
|
|
import DataTable, { Column } from "../../../components/common/DataTable";
|
|||
|
|
import { mockEvaluation360Templates } from "../../../mocks/mockEvaluation360Templates";
|
|||
|
|
import Widget from "../../../components/common/Widget";
|
|||
|
|
|
|||
|
|
const Degree360Templates: React.FC = () => {
|
|||
|
|
const [templateSearchTerm, setTemplateSearchTerm] = useState("");
|
|||
|
|
|
|||
|
|
// Modal states
|
|||
|
|
const [showTemplateModal, setShowTemplateModal] = useState(false);
|
|||
|
|
const [showQuestionModal, setShowQuestionModal] = useState(false);
|
|||
|
|
const [showQuestionItemModal, setShowQuestionItemModal] = useState(false);
|
|||
|
|
const [selectedTemplate, setSelectedTemplate] =
|
|||
|
|
useState<HrEvaluation360Template | null>(null);
|
|||
|
|
const [selectedQuestionGroup, setSelectedQuestionGroup] =
|
|||
|
|
useState<HrQuestionGroup | null>(null);
|
|||
|
|
const [selectedQuestion, setSelectedQuestion] =
|
|||
|
|
useState<HrEvaluationQuestion | null>(null);
|
|||
|
|
const [isEditMode, setIsEditMode] = useState(false);
|
|||
|
|
|
|||
|
|
// Form states
|
|||
|
|
const [templateForm, setTemplateForm] = useState({
|
|||
|
|
name: "",
|
|||
|
|
description: "",
|
|||
|
|
isActive: true,
|
|||
|
|
assessorTypes: [] as AssessorTypeEnum[],
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const [questionGroupForm, setQuestionGroupForm] = useState({
|
|||
|
|
groupName: "",
|
|||
|
|
description: "",
|
|||
|
|
weight: 0,
|
|||
|
|
order: 0,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const [questionForm, setQuestionForm] = useState({
|
|||
|
|
questionText: "",
|
|||
|
|
questionType: QuestionTypeEnum.Rating,
|
|||
|
|
isRequired: true,
|
|||
|
|
order: 0,
|
|||
|
|
weight: 0,
|
|||
|
|
options: [] as HrQuestionOption[],
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Filter templates
|
|||
|
|
const filteredTemplates = mockEvaluation360Templates.filter((template) => {
|
|||
|
|
const searchLower = templateSearchTerm.toLowerCase();
|
|||
|
|
return (
|
|||
|
|
template.name.toLowerCase().includes(searchLower) ||
|
|||
|
|
template.description.toLowerCase().includes(searchLower)
|
|||
|
|
);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Statistics
|
|||
|
|
const totalTemplates = mockEvaluation360Templates.length;
|
|||
|
|
const activeTemplates = mockEvaluation360Templates.filter(
|
|||
|
|
(t) => t.isActive
|
|||
|
|
).length;
|
|||
|
|
|
|||
|
|
// Event handlers
|
|||
|
|
const handleNewTemplate = () => {
|
|||
|
|
setTemplateForm({
|
|||
|
|
name: "",
|
|||
|
|
description: "",
|
|||
|
|
isActive: true,
|
|||
|
|
assessorTypes: [],
|
|||
|
|
});
|
|||
|
|
setSelectedTemplate(null);
|
|||
|
|
setIsEditMode(false);
|
|||
|
|
setShowTemplateModal(true);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleEditTemplate = (template: HrEvaluation360Template) => {
|
|||
|
|
setTemplateForm({
|
|||
|
|
name: template.name,
|
|||
|
|
description: template.description,
|
|||
|
|
isActive: template.isActive,
|
|||
|
|
assessorTypes: template.assessorTypes || [],
|
|||
|
|
});
|
|||
|
|
setSelectedTemplate(template);
|
|||
|
|
setIsEditMode(true);
|
|||
|
|
setShowTemplateModal(true);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleViewTemplate = (template: HrEvaluation360Template) => {
|
|||
|
|
setTemplateForm({
|
|||
|
|
name: template.name,
|
|||
|
|
description: template.description,
|
|||
|
|
isActive: template.isActive,
|
|||
|
|
assessorTypes: template.assessorTypes || [],
|
|||
|
|
});
|
|||
|
|
setSelectedTemplate(template);
|
|||
|
|
setShowTemplateModal(true);
|
|||
|
|
setIsEditMode(false);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleSaveTemplate = () => {
|
|||
|
|
console.log("Saving template:", templateForm);
|
|||
|
|
setShowTemplateModal(false);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleDeleteTemplate = (template: HrEvaluation360Template) => {
|
|||
|
|
if (
|
|||
|
|
confirm(
|
|||
|
|
`"${template.name}" şablonunu silmek istediğinizden emin misiniz?`
|
|||
|
|
)
|
|||
|
|
) {
|
|||
|
|
console.log("Deleting template:", template.id);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleAddQuestionGroup = () => {
|
|||
|
|
setQuestionGroupForm({
|
|||
|
|
groupName: "",
|
|||
|
|
description: "",
|
|||
|
|
weight: 0,
|
|||
|
|
order: 0,
|
|||
|
|
});
|
|||
|
|
setSelectedQuestionGroup(null);
|
|||
|
|
setShowQuestionModal(true);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleEditQuestionGroup = (group: HrQuestionGroup) => {
|
|||
|
|
setQuestionGroupForm({
|
|||
|
|
groupName: group.groupName,
|
|||
|
|
description: group.description || "",
|
|||
|
|
weight: group.weight,
|
|||
|
|
order: group.order,
|
|||
|
|
});
|
|||
|
|
setSelectedQuestionGroup(group);
|
|||
|
|
setShowQuestionModal(true);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleSaveQuestionGroup = () => {
|
|||
|
|
console.log("Saving question group:", questionGroupForm);
|
|||
|
|
setShowQuestionModal(false);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleAddQuestion = (group: HrQuestionGroup) => {
|
|||
|
|
setQuestionForm({
|
|||
|
|
questionText: "",
|
|||
|
|
questionType: QuestionTypeEnum.Rating,
|
|||
|
|
isRequired: true,
|
|||
|
|
order: 0,
|
|||
|
|
weight: 0,
|
|||
|
|
options: [],
|
|||
|
|
});
|
|||
|
|
setSelectedQuestion(null);
|
|||
|
|
setSelectedQuestionGroup(group);
|
|||
|
|
setShowQuestionItemModal(true);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleEditQuestion = (
|
|||
|
|
question: HrEvaluationQuestion,
|
|||
|
|
group: HrQuestionGroup
|
|||
|
|
) => {
|
|||
|
|
setQuestionForm({
|
|||
|
|
questionText: question.questionText,
|
|||
|
|
questionType: question.questionType,
|
|||
|
|
isRequired: question.isRequired,
|
|||
|
|
order: question.order,
|
|||
|
|
weight: question.weight,
|
|||
|
|
options: question.options || [],
|
|||
|
|
});
|
|||
|
|
setSelectedQuestion(question);
|
|||
|
|
setSelectedQuestionGroup(group);
|
|||
|
|
setShowQuestionItemModal(true);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleSaveQuestion = () => {
|
|||
|
|
console.log("Saving question:", questionForm);
|
|||
|
|
setShowQuestionItemModal(false);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// Table columns
|
|||
|
|
const templateColumns: Column<HrEvaluation360Template>[] = [
|
|||
|
|
{
|
|||
|
|
key: "name",
|
|||
|
|
header: "Şablon Adı",
|
|||
|
|
sortable: true,
|
|||
|
|
render: (template) => (
|
|||
|
|
<div>
|
|||
|
|
<div className="font-medium text-gray-900">{template.name}</div>
|
|||
|
|
<div className="text-sm text-gray-500">{template.description}</div>
|
|||
|
|
</div>
|
|||
|
|
),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
key: "questionGroups",
|
|||
|
|
header: "Soru Grupları",
|
|||
|
|
render: (template) => (
|
|||
|
|
<span className="text-sm text-gray-600">
|
|||
|
|
{template.questionGroups?.length || 0} grup
|
|||
|
|
</span>
|
|||
|
|
),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
key: "assessorTypes",
|
|||
|
|
header: "Değerlendirecekler",
|
|||
|
|
render: (template) => {
|
|||
|
|
const assessorLabels = {
|
|||
|
|
[AssessorTypeEnum.Self]: "Kendi",
|
|||
|
|
[AssessorTypeEnum.Manager]: "Yönetici",
|
|||
|
|
[AssessorTypeEnum.Peer]: "Meslektaş",
|
|||
|
|
[AssessorTypeEnum.Subordinate]: "Ast",
|
|||
|
|
[AssessorTypeEnum.Customer]: "Müşteri",
|
|||
|
|
[AssessorTypeEnum.OtherDepartment]: "Diğer Departman",
|
|||
|
|
[AssessorTypeEnum.HRUpperManagement]: "İK/Üst Yönetim",
|
|||
|
|
[AssessorTypeEnum.External]: "Dış Paydaş",
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const assessorTypes = template.assessorTypes || [];
|
|||
|
|
return (
|
|||
|
|
<div className="flex flex-wrap gap-1">
|
|||
|
|
{assessorTypes.map((type) => (
|
|||
|
|
<span
|
|||
|
|
key={type}
|
|||
|
|
className="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-800"
|
|||
|
|
>
|
|||
|
|
{assessorLabels[type] || type}
|
|||
|
|
</span>
|
|||
|
|
))}
|
|||
|
|
{assessorTypes.length === 0 && (
|
|||
|
|
<span className="text-sm text-gray-400">Belirtilmemiş</span>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
key: "isActive",
|
|||
|
|
header: "Durum",
|
|||
|
|
render: (template) => (
|
|||
|
|
<span
|
|||
|
|
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
|||
|
|
template.isActive
|
|||
|
|
? "bg-green-100 text-green-800"
|
|||
|
|
: "bg-red-100 text-red-800"
|
|||
|
|
}`}
|
|||
|
|
>
|
|||
|
|
{template.isActive ? "Aktif" : "Pasif"}
|
|||
|
|
</span>
|
|||
|
|
),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
key: "actions",
|
|||
|
|
header: "İşlemler",
|
|||
|
|
render: (template) => (
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
<button
|
|||
|
|
onClick={() => handleViewTemplate(template)}
|
|||
|
|
className="text-blue-600 hover:text-blue-900"
|
|||
|
|
title="Görüntüle"
|
|||
|
|
>
|
|||
|
|
<FaEye className="w-4 h-4" />
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
onClick={() => handleEditTemplate(template)}
|
|||
|
|
className="text-green-600 hover:text-green-900"
|
|||
|
|
title="Düzenle"
|
|||
|
|
>
|
|||
|
|
<FaEdit className="w-4 h-4" />
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
onClick={() => handleDeleteTemplate(template)}
|
|||
|
|
className="text-red-600 hover:text-red-900"
|
|||
|
|
title="Sil"
|
|||
|
|
>
|
|||
|
|
<FaTrash className="w-4 h-4" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
),
|
|||
|
|
},
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="space-y-4 mt-2">
|
|||
|
|
{/* Header */}
|
|||
|
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
|||
|
|
<div>
|
|||
|
|
<h2 className="text-xl font-bold text-gray-900">360° Şablonlar</h2>
|
|||
|
|
<p className="text-sm text-gray-600 mt-1">
|
|||
|
|
360 derece değerlendirme şablonlarını yönetin
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<button
|
|||
|
|
onClick={handleNewTemplate}
|
|||
|
|
className="flex items-center gap-2 px-3 py-1.5 bg-purple-600 text-white rounded-md hover:bg-purple-700 ml-4 text-sm"
|
|||
|
|
>
|
|||
|
|
<FaPlus className="w-4 h-4" />
|
|||
|
|
Yeni Şablon
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Statistics */}
|
|||
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|||
|
|
<Widget
|
|||
|
|
title="Toplam Şablon"
|
|||
|
|
value={totalTemplates}
|
|||
|
|
color="purple"
|
|||
|
|
icon="FaClipboardList"
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<Widget
|
|||
|
|
title="Aktif Şablon"
|
|||
|
|
value={activeTemplates}
|
|||
|
|
color="green"
|
|||
|
|
icon="FaClipboardList"
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<Widget
|
|||
|
|
title="Pasif Şablon"
|
|||
|
|
value={totalTemplates - activeTemplates}
|
|||
|
|
color="red"
|
|||
|
|
icon="FaClipboardList"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="bg-white p-4 rounded-lg border">
|
|||
|
|
<div className="flex-1">
|
|||
|
|
<input
|
|||
|
|
type="text"
|
|||
|
|
placeholder="Şablon adı veya açıklama ile ara..."
|
|||
|
|
value={templateSearchTerm}
|
|||
|
|
onChange={(e) => setTemplateSearchTerm(e.target.value)}
|
|||
|
|
className="w-full border border-gray-300 rounded-md px-3 py-1.5 text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Templates Table */}
|
|||
|
|
<DataTable data={filteredTemplates} columns={templateColumns} />
|
|||
|
|
|
|||
|
|
{/* Template Modal */}
|
|||
|
|
{showTemplateModal && (
|
|||
|
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
|||
|
|
<div className="bg-white rounded-lg p-6 w-full max-w-4xl max-h-[90vh] overflow-y-auto">
|
|||
|
|
<div className="flex justify-between items-center mb-4">
|
|||
|
|
<h2 className="text-lg font-bold">
|
|||
|
|
{isEditMode
|
|||
|
|
? "Şablon Düzenle"
|
|||
|
|
: selectedTemplate
|
|||
|
|
? "Şablon Detayı"
|
|||
|
|
: "Yeni Şablon"}
|
|||
|
|
</h2>
|
|||
|
|
<button
|
|||
|
|
onClick={() => setShowTemplateModal(false)}
|
|||
|
|
className="p-2 text-gray-500 hover:text-gray-700"
|
|||
|
|
>
|
|||
|
|
<FaTimes className="w-5 h-5" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="space-y-4">
|
|||
|
|
{/* Basic Information */}
|
|||
|
|
<div className="grid grid-cols-2 gap-4">
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|||
|
|
Şablon Adı
|
|||
|
|
</label>
|
|||
|
|
<input
|
|||
|
|
type="text"
|
|||
|
|
value={templateForm.name}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setTemplateForm({ ...templateForm, name: e.target.value })
|
|||
|
|
}
|
|||
|
|
className="w-full border border-gray-300 rounded-md px-3 py-1.5 text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
|||
|
|
disabled={!isEditMode && !!selectedTemplate}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|||
|
|
Durum
|
|||
|
|
</label>
|
|||
|
|
<select
|
|||
|
|
value={templateForm.isActive ? "active" : "inactive"}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setTemplateForm({
|
|||
|
|
...templateForm,
|
|||
|
|
isActive: e.target.value === "active",
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
className="w-full border border-gray-300 rounded-md px-3 py-1.5 text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
|||
|
|
disabled={!isEditMode && !!selectedTemplate}
|
|||
|
|
>
|
|||
|
|
<option value="active">Aktif</option>
|
|||
|
|
<option value="inactive">Pasif</option>
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|||
|
|
Açıklama
|
|||
|
|
</label>
|
|||
|
|
<textarea
|
|||
|
|
value={templateForm.description}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setTemplateForm({
|
|||
|
|
...templateForm,
|
|||
|
|
description: e.target.value,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
rows={3}
|
|||
|
|
className="w-full border border-gray-300 rounded-md px-3 py-1.5 text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
|||
|
|
disabled={!isEditMode && !!selectedTemplate}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Değerlendiriciler */}
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|||
|
|
Değerlendirecekler
|
|||
|
|
</label>
|
|||
|
|
<div className="grid grid-cols-2 gap-3">
|
|||
|
|
{Object.values(AssessorTypeEnum).map((type) => {
|
|||
|
|
const labels = {
|
|||
|
|
[AssessorTypeEnum.Self]: "Kendi",
|
|||
|
|
[AssessorTypeEnum.Manager]: "Yönetici",
|
|||
|
|
[AssessorTypeEnum.Peer]: "Meslektaş",
|
|||
|
|
[AssessorTypeEnum.Subordinate]: "Ast",
|
|||
|
|
[AssessorTypeEnum.Customer]: "Müşteri",
|
|||
|
|
[AssessorTypeEnum.OtherDepartment]: "Diğer Departman",
|
|||
|
|
[AssessorTypeEnum.HRUpperManagement]: "İK/Üst Yönetim",
|
|||
|
|
[AssessorTypeEnum.External]: "Dış Paydaş",
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<label key={type} className="flex items-center space-x-2">
|
|||
|
|
<input
|
|||
|
|
type="checkbox"
|
|||
|
|
checked={templateForm.assessorTypes.includes(type)}
|
|||
|
|
onChange={(e) => {
|
|||
|
|
if (e.target.checked) {
|
|||
|
|
setTemplateForm({
|
|||
|
|
...templateForm,
|
|||
|
|
assessorTypes: [
|
|||
|
|
...templateForm.assessorTypes,
|
|||
|
|
type,
|
|||
|
|
],
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
setTemplateForm({
|
|||
|
|
...templateForm,
|
|||
|
|
assessorTypes:
|
|||
|
|
templateForm.assessorTypes.filter(
|
|||
|
|
(t) => t !== type
|
|||
|
|
),
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}}
|
|||
|
|
className="rounded border-gray-300 text-purple-600 focus:ring-purple-500"
|
|||
|
|
disabled={!isEditMode && !!selectedTemplate}
|
|||
|
|
/>
|
|||
|
|
<span className="text-sm text-gray-700">
|
|||
|
|
{labels[type] || type}
|
|||
|
|
</span>
|
|||
|
|
</label>
|
|||
|
|
);
|
|||
|
|
})}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Question Groups */}
|
|||
|
|
{selectedTemplate && (
|
|||
|
|
<div>
|
|||
|
|
<div className="flex justify-between items-center mb-4">
|
|||
|
|
<h3 className="text-lg font-semibold">Soru Grupları</h3>
|
|||
|
|
{isEditMode && (
|
|||
|
|
<button
|
|||
|
|
onClick={handleAddQuestionGroup}
|
|||
|
|
className="flex items-center gap-2 px-2 py-1 bg-blue-600 text-white rounded-md hover:bg-blue-700 text-xs"
|
|||
|
|
>
|
|||
|
|
<FaPlus className="w-3 h-3" />
|
|||
|
|
Grup Ekle
|
|||
|
|
</button>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="space-y-3">
|
|||
|
|
{selectedTemplate.questionGroups?.map(
|
|||
|
|
(group, groupIndex) => (
|
|||
|
|
<div
|
|||
|
|
key={groupIndex}
|
|||
|
|
className="bg-gray-50 p-3 rounded-lg"
|
|||
|
|
>
|
|||
|
|
<div className="flex justify-between items-start mb-3">
|
|||
|
|
<div>
|
|||
|
|
<h4 className="font-medium">{group.groupName}</h4>
|
|||
|
|
<p className="text-sm text-gray-600">
|
|||
|
|
{group.description}
|
|||
|
|
</p>
|
|||
|
|
<p className="text-sm text-gray-600">
|
|||
|
|
Ağırlık: {group.weight}%
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
{isEditMode && (
|
|||
|
|
<div className="flex gap-2">
|
|||
|
|
<button
|
|||
|
|
onClick={() => handleEditQuestionGroup(group)}
|
|||
|
|
className="text-blue-600 hover:text-blue-900"
|
|||
|
|
>
|
|||
|
|
<FaEdit className="w-4 h-4" />
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
onClick={() => handleAddQuestion(group)}
|
|||
|
|
className="text-green-600 hover:text-green-900"
|
|||
|
|
>
|
|||
|
|
<FaQuestionCircle className="w-4 h-4" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Questions */}
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
{group.questions?.map((question, questionIndex) => (
|
|||
|
|
<div
|
|||
|
|
key={questionIndex}
|
|||
|
|
className="bg-white p-2 rounded border"
|
|||
|
|
>
|
|||
|
|
<div className="flex justify-between items-start">
|
|||
|
|
<div className="flex-1">
|
|||
|
|
<p className="text-sm">
|
|||
|
|
{question.questionText}
|
|||
|
|
</p>
|
|||
|
|
<div className="flex gap-4 mt-1">
|
|||
|
|
<span className="text-xs text-gray-500">
|
|||
|
|
Tip:{" "}
|
|||
|
|
{question.questionType ===
|
|||
|
|
QuestionTypeEnum.Rating
|
|||
|
|
? "Puanlama"
|
|||
|
|
: question.questionType ===
|
|||
|
|
QuestionTypeEnum.MultipleChoice
|
|||
|
|
? "Çoktan Seçmeli"
|
|||
|
|
: question.questionType ===
|
|||
|
|
QuestionTypeEnum.Text
|
|||
|
|
? "Metin"
|
|||
|
|
: "Evet/Hayır"}
|
|||
|
|
</span>
|
|||
|
|
<span className="text-xs text-gray-500">
|
|||
|
|
Ağırlık: {question.weight}%
|
|||
|
|
</span>
|
|||
|
|
{question.isRequired && (
|
|||
|
|
<span className="text-xs text-red-500">
|
|||
|
|
Zorunlu
|
|||
|
|
</span>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
{isEditMode && (
|
|||
|
|
<button
|
|||
|
|
onClick={() =>
|
|||
|
|
handleEditQuestion(question, group)
|
|||
|
|
}
|
|||
|
|
className="text-blue-600 hover:text-blue-900"
|
|||
|
|
>
|
|||
|
|
<FaEdit className="w-4 h-4" />
|
|||
|
|
</button>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="flex justify-end gap-4 mt-4">
|
|||
|
|
<button
|
|||
|
|
onClick={() => setShowTemplateModal(false)}
|
|||
|
|
className="px-3 py-1.5 text-sm border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50"
|
|||
|
|
>
|
|||
|
|
{selectedTemplate && !isEditMode ? "Kapat" : "İptal"}
|
|||
|
|
</button>
|
|||
|
|
{(isEditMode || !selectedTemplate) && (
|
|||
|
|
<button
|
|||
|
|
onClick={handleSaveTemplate}
|
|||
|
|
className="flex items-center gap-2 px-3 py-1.5 text-sm bg-purple-600 text-white rounded-md hover:bg-purple-700"
|
|||
|
|
>
|
|||
|
|
<FaSave className="w-4 h-4" />
|
|||
|
|
Kaydet
|
|||
|
|
</button>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{/* Question Group Modal */}
|
|||
|
|
{showQuestionModal && (
|
|||
|
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
|||
|
|
<div className="bg-white rounded-lg p-6 w-full max-w-md">
|
|||
|
|
<div className="flex justify-between items-center mb-4">
|
|||
|
|
<h2 className="text-lg font-bold">
|
|||
|
|
{selectedQuestionGroup
|
|||
|
|
? "Soru Grubu Düzenle"
|
|||
|
|
: "Yeni Soru Grubu"}
|
|||
|
|
</h2>
|
|||
|
|
<button
|
|||
|
|
onClick={() => setShowQuestionModal(false)}
|
|||
|
|
className="p-2 text-gray-500 hover:text-gray-700"
|
|||
|
|
>
|
|||
|
|
<FaTimes className="w-5 h-5" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="space-y-4">
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|||
|
|
Grup Adı
|
|||
|
|
</label>
|
|||
|
|
<input
|
|||
|
|
type="text"
|
|||
|
|
value={questionGroupForm.groupName}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setQuestionGroupForm({
|
|||
|
|
...questionGroupForm,
|
|||
|
|
groupName: e.target.value,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
className="w-full border border-gray-300 rounded-md px-3 py-1.5 text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|||
|
|
Açıklama
|
|||
|
|
</label>
|
|||
|
|
<textarea
|
|||
|
|
value={questionGroupForm.description}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setQuestionGroupForm({
|
|||
|
|
...questionGroupForm,
|
|||
|
|
description: e.target.value,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
rows={3}
|
|||
|
|
className="w-full border border-gray-300 rounded-md px-3 py-1.5 text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="grid grid-cols-2 gap-4">
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|||
|
|
Ağırlık (%)
|
|||
|
|
</label>
|
|||
|
|
<input
|
|||
|
|
type="number"
|
|||
|
|
min="0"
|
|||
|
|
max="100"
|
|||
|
|
value={questionGroupForm.weight}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setQuestionGroupForm({
|
|||
|
|
...questionGroupForm,
|
|||
|
|
weight: Number(e.target.value),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
className="w-full border border-gray-300 rounded-md px-3 py-1.5 text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|||
|
|
Sıra
|
|||
|
|
</label>
|
|||
|
|
<input
|
|||
|
|
type="number"
|
|||
|
|
min="0"
|
|||
|
|
value={questionGroupForm.order}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setQuestionGroupForm({
|
|||
|
|
...questionGroupForm,
|
|||
|
|
order: Number(e.target.value),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
className="w-full border border-gray-300 rounded-md px-3 py-1.5 text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="flex justify-end gap-4 mt-4">
|
|||
|
|
<button
|
|||
|
|
onClick={() => setShowQuestionModal(false)}
|
|||
|
|
className="px-3 py-1.5 text-sm border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50"
|
|||
|
|
>
|
|||
|
|
İptal
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
onClick={handleSaveQuestionGroup}
|
|||
|
|
className="flex items-center gap-2 px-3 py-1.5 text-sm bg-purple-600 text-white rounded-md hover:bg-purple-700"
|
|||
|
|
>
|
|||
|
|
<FaSave className="w-4 h-4" />
|
|||
|
|
Kaydet
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{/* Question Modal */}
|
|||
|
|
{showQuestionItemModal && (
|
|||
|
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
|||
|
|
<div className="bg-white rounded-lg p-6 w-full max-w-md">
|
|||
|
|
<div className="flex justify-between items-center mb-4">
|
|||
|
|
<h2 className="text-lg font-bold">
|
|||
|
|
{selectedQuestion ? "Soru Düzenle" : "Yeni Soru"}
|
|||
|
|
</h2>
|
|||
|
|
<button
|
|||
|
|
onClick={() => setShowQuestionItemModal(false)}
|
|||
|
|
className="p-2 text-gray-500 hover:text-gray-700"
|
|||
|
|
>
|
|||
|
|
<FaTimes className="w-5 h-5" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="space-y-4">
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|||
|
|
Soru Metni
|
|||
|
|
</label>
|
|||
|
|
<textarea
|
|||
|
|
value={questionForm.questionText}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setQuestionForm({
|
|||
|
|
...questionForm,
|
|||
|
|
questionText: e.target.value,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
rows={3}
|
|||
|
|
className="w-full border border-gray-300 rounded-md px-3 py-1.5 text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|||
|
|
Soru Tipi
|
|||
|
|
</label>
|
|||
|
|
<select
|
|||
|
|
value={questionForm.questionType}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setQuestionForm({
|
|||
|
|
...questionForm,
|
|||
|
|
questionType: e.target.value as QuestionTypeEnum,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
className="w-full border border-gray-300 rounded-md px-3 py-1.5 text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
|||
|
|
>
|
|||
|
|
<option value={QuestionTypeEnum.Rating}>Puanlama</option>
|
|||
|
|
<option value={QuestionTypeEnum.MultipleChoice}>
|
|||
|
|
Çoktan Seçmeli
|
|||
|
|
</option>
|
|||
|
|
<option value={QuestionTypeEnum.Text}>Metin</option>
|
|||
|
|
<option value={QuestionTypeEnum.YesNo}>Evet/Hayır</option>
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="grid grid-cols-2 gap-4">
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|||
|
|
Ağırlık (%)
|
|||
|
|
</label>
|
|||
|
|
<input
|
|||
|
|
type="number"
|
|||
|
|
min="0"
|
|||
|
|
max="100"
|
|||
|
|
value={questionForm.weight}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setQuestionForm({
|
|||
|
|
...questionForm,
|
|||
|
|
weight: Number(e.target.value),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
className="w-full border border-gray-300 rounded-md px-3 py-1.5 text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
<div>
|
|||
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|||
|
|
Sıra
|
|||
|
|
</label>
|
|||
|
|
<input
|
|||
|
|
type="number"
|
|||
|
|
min="0"
|
|||
|
|
value={questionForm.order}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setQuestionForm({
|
|||
|
|
...questionForm,
|
|||
|
|
order: Number(e.target.value),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
className="w-full border border-gray-300 rounded-md px-3 py-1.5 text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="flex items-center">
|
|||
|
|
<input
|
|||
|
|
type="checkbox"
|
|||
|
|
id="isRequired"
|
|||
|
|
checked={questionForm.isRequired}
|
|||
|
|
onChange={(e) =>
|
|||
|
|
setQuestionForm({
|
|||
|
|
...questionForm,
|
|||
|
|
isRequired: e.target.checked,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
className="h-4 w-4 text-purple-600 focus:ring-purple-500 border-gray-300 rounded"
|
|||
|
|
/>
|
|||
|
|
<label
|
|||
|
|
htmlFor="isRequired"
|
|||
|
|
className="ml-2 block text-sm text-gray-700"
|
|||
|
|
>
|
|||
|
|
Zorunlu soru
|
|||
|
|
</label>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="flex justify-end gap-4 mt-4">
|
|||
|
|
<button
|
|||
|
|
onClick={() => setShowQuestionItemModal(false)}
|
|||
|
|
className="px-3 py-1.5 text-sm border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50"
|
|||
|
|
>
|
|||
|
|
İptal
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
onClick={handleSaveQuestion}
|
|||
|
|
className="flex items-center gap-2 px-3 py-1.5 text-sm bg-purple-600 text-white rounded-md hover:bg-purple-700"
|
|||
|
|
>
|
|||
|
|
<FaSave className="w-4 h-4" />
|
|||
|
|
Kaydet
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export default Degree360Templates;
|