بالنسبة الاجابة على السؤال بخصوص حقل "الموقع" (Location)يكون حقلاً تفاعلياً يفتح خريطة (مثل Google Maps) ليختار المستخدم الموقع منها
----------
views/admin/form-templates/create.ejs
<%- include('../partials/head') %>
بناء نموذج جديد
<%- include('../partials/sidebar') %>
---------------
views/admin/form-templates/edit.ejs
<%- include('../partials/head') %>
<%= pageTitle %>
<%- include('../partials/sidebar') %>
تعديل النموذج: <%= template.name %>
-------------------
controllers/formTemplateController.js
const FormTemplate = require('../models/formTemplate');
const ApprovalWorkflowTemplate = require('../models/approvalWorkflowTemplate');
exports.getAllTemplates = async (req, res) => {
try {
const templates = await FormTemplate.find().populate('createdBy', 'displayName').sort({ createdAt: -1 });
res.render('form-templates/index', { pageTitle: "Form Templates", templates });
} catch (error) { res.status(500).send(error.message); }
};
exports.getCreateTemplatePage = async (req, res) => {
try {
const approvalWorkflows = await ApprovalWorkflowTemplate.find();
res.render('form-templates/create', { pageTitle: "Build New Form Template", approvalWorkflows });
} catch (error) { res.status(500).send(error.message); }
};
exports.createTemplate = async (req, res) => {
try {
const { name, description, fields, approvalWorkflow } = req.body;
if (!name || !fields) {
return res.status(400).send("Name and fields are required.");
}
const parsedFields = JSON.parse(fields);
await FormTemplate.create({
name, description, fields: parsedFields,
createdBy: req.session.userId,
approvalWorkflow: approvalWorkflow || null
});
res.redirect('/form-templates');
} catch (error) { res.status(400).send(error.message); }
};
exports.getTemplateDetails = async (req, res) => {
try {
const template = await FormTemplate.findById(req.params.id).populate('createdBy', 'displayName').populate('approvalWorkflow', 'name');
if (!template) return res.status(404).send('Template not found');
res.render('form-templates/show', { pageTitle: `Details: ${template.name}`, template });
} catch (error) { res.status(500).send(error.message); }
};
exports.getEditTemplatePage = async (req, res) => {
try {
const template = await FormTemplate.findById(req.params.id);
if (!template) return res.status(404).send('Template not found');
const approvalWorkflows = await ApprovalWorkflowTemplate.find();
res.render('form-templates/edit', { pageTitle: `Edit: ${template.name}`, template, approvalWorkflows });
} catch (error) { res.status(500).send(error.message); }
};
exports.updateTemplate = async (req, res) => {
try {
const { name, description, fields, approvalWorkflow } = req.body;
const parsedFields = JSON.parse(fields);
await FormTemplate.findByIdAndUpdate(req.params.id, {
name, description, fields: parsedFields,
approvalWorkflow: approvalWorkflow || null
});
res.redirect('/form-templates');
} catch (error) { res.status(400).send(error.message); }
};
exports.deleteTemplate = async (req, res) => {
try {
await FormTemplate.findByIdAndDelete(req.params.id);
res.redirect('/form-templates');
} catch (error) { res.status(500).send(error.message); }
};
-----------------
controllers/formSubmissionController.js غير موجود عندي وموجودله ملف في models
----------------
controllers/approvalWorkflowController.js غير موجود عندي وموجودله ملف في models
----------------
middleware/multerConfig.js
const multer = require('multer');
const path = require('path');
// إعدادات التخزين
const storage = multer.diskStorage({
// تحديد مجلد الحفظ
destination: function (req, file, cb) {
cb(null, 'uploads/'); // تأكد من وجود مجلد باسم uploads في جذر المشروع
},
// تحديد اسم الملف
filename: function (req, file, cb) {
// إضافة تاريخ فريد للاسم لمنع تكرار الأسماء
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, uniqueSuffix + '-' + file.originalname);
}
});
// إعداد كائن multer
const upload = multer({ storage: storage });
module.exports = upload;
--------------------
models/formTemplate.js
const mongoose = require('mongoose');
// This defines a single field within your form (e.g., a text input, a date picker)
const formFieldSchema = new mongoose.Schema({
label: { type: String, required: true }, // The question or title of the field (e.g., "Engineer's Name")
name: { type: String, required: true }, // A unique machine-readable name (e.g., "engineer_name")
fieldType: {
type: String,
required: true,
enum: [
'text',
'textarea',
'number',
'date',
'select',
'radio',
'checkbox',
'project-select',
'user-select',
'signature',
'file', // <-- ✨ إضافة جديدة
'location' // <-- ✨ إضافة جديدة
]
},
options: [{ type: String }], // Only used if fieldType is 'select', 'radio', or 'checkbox'
required: { type: Boolean, default: false }
}, { _id: false }); // We don't need a separate ID for each field
const formTemplateSchema = new mongoose.Schema({
name: { type: String, required: true, unique: true }, // e.g., "Field Visit Report"
description: { type: String },
fields: [formFieldSchema], // An array of all the fields in this form
createdBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
// This links the form to a specific approval workflow template
approvalWorkflow: {
type: mongoose.Schema.Types.ObjectId,
ref: 'ApprovalWorkflowTemplate',
required: false
}
}, { timestamps: true });
module.exports = mongoose.model('FormTemplate', formTemplateSchema);
--------------
models/formSubmission.js
const mongoose = require('mongoose');
const formSubmissionSchema = new mongoose.Schema({
template: {
type: mongoose.Schema.Types.ObjectId,
ref: 'FormTemplate',
required: true
},
project: { // Link the submission to a specific project
type: mongoose.Schema.Types.ObjectId,
ref: 'Project',
required: true
},
submittedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
// This will store the actual data as a flexible object, e.g., { engineer_name: "John Doe", visit_date: "2025-10-05" }
data: {
type: mongoose.Schema.Types.Mixed,
required: true
}, { timestamps: true });
}
module.exports = mongoose.model('FormSubmission', formSubmissionSchema);
-------------------