mvp.9
This commit is contained in:
parent
3776a03a8f
commit
953eb569ce
@ -17,6 +17,20 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$sql = "INSERT INTO processes (name, description) VALUES (?, ?)";
|
$sql = "INSERT INTO processes (name, description) VALUES (?, ?)";
|
||||||
$stmt = $pdo->prepare($sql);
|
$stmt = $pdo->prepare($sql);
|
||||||
$stmt->execute([$name, $description]);
|
$stmt->execute([$name, $description]);
|
||||||
|
|
||||||
|
$process_id = $pdo->lastInsertId();
|
||||||
|
|
||||||
|
// Insert process steps if available
|
||||||
|
if (isset($_POST['steps']) && is_array($_POST['steps'])) {
|
||||||
|
$step_order = 0;
|
||||||
|
$stmt_steps = $pdo->prepare("INSERT INTO process_steps (process_id, title, description, step_order) VALUES (?, ?, ?, ?)");
|
||||||
|
foreach ($_POST['steps'] as $step) {
|
||||||
|
if (isset($step['title']) && isset($step['description'])) {
|
||||||
|
$stmt_steps->execute([$process_id, $step['title'], $step['description'], $step_order]);
|
||||||
|
$step_order++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
// In a real app, log this error. For now, we'll just die.
|
// In a real app, log this error. For now, we'll just die.
|
||||||
error_log("DB Error: " . $e->getMessage());
|
error_log("DB Error: " . $e->getMessage());
|
||||||
|
|||||||
@ -5,7 +5,7 @@ require_once __DIR__ . '/../ai/LocalAIApi.php';
|
|||||||
|
|
||||||
$keyword = $_GET['keyword'] ?? 'new process';
|
$keyword = $_GET['keyword'] ?? 'new process';
|
||||||
|
|
||||||
$prompt = "Generate a creative and concise name and a short description for a process related to '" . htmlspecialchars($keyword) . "'. Respond in JSON format with 'name' and 'description' keys. Example: {"name": "Automated Workflow Orchestrator", "description": "Manages and streamlines complex business workflows."}";
|
$prompt = "Generate a creative and concise name, a short description, and a list of detailed steps for a business process related to '" . htmlspecialchars($keyword) . "'. Each step should have a 'title' and a 'description'. Respond in JSON format with 'name', 'description', and 'steps' keys. Example: {"name": "Employee Onboarding Process", "description": "A systematic process to integrate new hires into the company.", "steps": [{"title": "Send welcome email", "description": "Automatically send a welcome email with company resources."}, {"title": "Setup IT access", "description": "Provision access to internal systems and tools."}]}";
|
||||||
|
|
||||||
$params = [
|
$params = [
|
||||||
'input' => [
|
'input' => [
|
||||||
@ -19,7 +19,7 @@ $response = LocalAIApi::createResponse($params);
|
|||||||
|
|
||||||
if (!empty($response['success'])) {
|
if (!empty($response['success'])) {
|
||||||
$decoded = LocalAIApi::decodeJsonFromResponse($response);
|
$decoded = LocalAIApi::decodeJsonFromResponse($response);
|
||||||
if ($decoded && isset($decoded['name']) && isset($decoded['description'])) {
|
if ($decoded && isset($decoded['name']) && isset($decoded['description']) && isset($decoded['steps'])) {
|
||||||
echo json_encode(['success' => true, 'data' => $decoded]);
|
echo json_encode(['success' => true, 'data' => $decoded]);
|
||||||
} else {
|
} else {
|
||||||
// Fallback if AI didn't return valid JSON or missing keys
|
// Fallback if AI didn't return valid JSON or missing keys
|
||||||
|
|||||||
9
db/migrations/20230102_create_process_steps_table.sql
Normal file
9
db/migrations/20230102_create_process_steps_table.sql
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS process_steps (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
process_id INT NOT NULL,
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
step_order INT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (process_id) REFERENCES processes(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
@ -13,7 +13,12 @@ if (isset($_GET['id'])) {
|
|||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$process = $stmt->fetch(PDO::FETCH_ASSOC);
|
$process = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if (!$process) {
|
if ($process) {
|
||||||
|
$stmt_steps = $pdo->prepare("SELECT id, title, description, step_order FROM process_steps WHERE process_id = :process_id ORDER BY step_order ASC");
|
||||||
|
$stmt_steps->bindParam(':process_id', $id, PDO::PARAM_INT);
|
||||||
|
$stmt_steps->execute();
|
||||||
|
$process['steps'] = $stmt_steps->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} else {
|
||||||
$error = "Process not found.";
|
$error = "Process not found.";
|
||||||
}
|
}
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
@ -68,6 +73,33 @@ $project_name = htmlspecialchars($_SERVER['PROJECT_NAME'] ?? 'ProcessFlow Optimi
|
|||||||
<label for="description" class="form-label">Description</label>
|
<label for="description" class="form-label">Description</label>
|
||||||
<textarea class="form-control" id="description" name="description" rows="4" required><?php echo htmlspecialchars($process['description']); ?></textarea>
|
<textarea class="form-control" id="description" name="description" rows="4" required><?php echo htmlspecialchars($process['description']); ?></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<hr class="my-4">
|
||||||
|
<h5 class="fw-bold mb-3">Process Steps</h5>
|
||||||
|
<div id="processStepsContainer">
|
||||||
|
<?php if (!empty($process['steps'])): ?>
|
||||||
|
<?php foreach ($process['steps'] as $index => $step): ?>
|
||||||
|
<div class="mb-3 p-3 border rounded bg-light position-relative process-step-item">
|
||||||
|
<input type="hidden" name="steps[<?php echo $index; ?>][id]" value="<?php echo htmlspecialchars($step['id']); ?>">
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="stepTitle_<?php echo $index; ?>" class="form-label fw-bold">Step <?php echo $index + 1; ?> Title</label>
|
||||||
|
<input type="text" class="form-control" id="stepTitle_<?php echo $index; ?>" name="steps[<?php echo $index; ?>][title]" value="<?php echo htmlspecialchars($step['title']); ?>" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="stepDescription_<?php echo $index; ?>" class="form-label">Step <?php echo $index + 1; ?> Description</label>
|
||||||
|
<textarea class="form-control" id="stepDescription_<?php echo $index; ?>" name="steps[<?php echo $index; ?>][description]" rows="2" required><?php echo htmlspecialchars($step['description']); ?></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-sm btn-danger position-absolute top-0 end-0 mt-2 me-2 remove-step-btn" title="Remove Step">
|
||||||
|
<i data-feather="x" style="width: 16px; height: 16px;"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<button type="button" id="addStepBtn" class="btn btn-outline-secondary btn-sm mb-4">
|
||||||
|
<i data-feather="plus" style="width: 16px; height: 16px;"></i> Add New Step
|
||||||
|
</button>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">Update Process</button>
|
<button type="submit" class="btn btn-primary">Update Process</button>
|
||||||
<a href="index.php" class="btn btn-link">Cancel</a>
|
<a href="index.php" class="btn btn-link">Cancel</a>
|
||||||
</form>
|
</form>
|
||||||
@ -85,6 +117,57 @@ $project_name = htmlspecialchars($_SERVER['PROJECT_NAME'] ?? 'ProcessFlow Optimi
|
|||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
feather.replace()
|
feather.replace()
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const processStepsContainer = document.getElementById('processStepsContainer');
|
||||||
|
const addStepBtn = document.getElementById('addStepBtn');
|
||||||
|
|
||||||
|
let stepIndex = processStepsContainer.children.length; // Start index after existing steps
|
||||||
|
|
||||||
|
function updateStepNumbers() {
|
||||||
|
Array.from(processStepsContainer.children).forEach((stepItem, index) => {
|
||||||
|
stepItem.querySelector('label[for^="stepTitle_"]').textContent = `Step ${index + 1} Title`;
|
||||||
|
stepItem.querySelector('label[for^="stepDescription_"]').textContent = `Step ${index + 1} Description`;
|
||||||
|
|
||||||
|
// Update name attributes for proper form submission
|
||||||
|
stepItem.querySelector('input[name$="[title]"]').name = `steps[${index}][title]`;
|
||||||
|
stepItem.querySelector('textarea[name$="[description]"]').name = `steps[${index}][description]`;
|
||||||
|
const hiddenIdInput = stepItem.querySelector('input[name$="[id]"]');
|
||||||
|
if (hiddenIdInput) {
|
||||||
|
hiddenIdInput.name = `steps[${index}][id]`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addStepBtn.addEventListener('click', function() {
|
||||||
|
const newStepHtml = `
|
||||||
|
<div class="mb-3 p-3 border rounded bg-light position-relative process-step-item">
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="stepTitle_${stepIndex}" class="form-label fw-bold">Step ${stepIndex + 1} Title</label>
|
||||||
|
<input type="text" class="form-control" id="stepTitle_${stepIndex}" name="steps[${stepIndex}][title]" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="stepDescription_${stepIndex}" class="form-label">Step ${stepIndex + 1} Description</label>
|
||||||
|
<textarea class="form-control" id="stepDescription_${stepIndex}" name="steps[${stepIndex}][description]" rows="2" required></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-sm btn-danger position-absolute top-0 end-0 mt-2 me-2 remove-step-btn" title="Remove Step">
|
||||||
|
<i data-feather="x" style="width: 16px; height: 16px;"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
processStepsContainer.insertAdjacentHTML('beforeend', newStepHtml);
|
||||||
|
feather.replace(); // Re-render feather icons for new button
|
||||||
|
stepIndex++;
|
||||||
|
updateStepNumbers();
|
||||||
|
});
|
||||||
|
|
||||||
|
processStepsContainer.addEventListener('click', function(event) {
|
||||||
|
if (event.target.closest('.remove-step-btn')) {
|
||||||
|
event.target.closest('.process-step-item').remove();
|
||||||
|
updateStepNumbers();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
41
index.php
41
index.php
@ -161,6 +161,10 @@ $project_image_url = htmlspecialchars($_SERVER['PROJECT_IMAGE_URL'] ?? '');
|
|||||||
<label for="description" class="form-label">Description</label>
|
<label for="description" class="form-label">Description</label>
|
||||||
<textarea class="form-control" id="description" name="description" rows="4" required></textarea>
|
<textarea class="form-control" id="description" name="description" rows="4" required></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="aiGeneratedSteps" class="mb-3 border p-3 rounded" style="display:none;">
|
||||||
|
<h6>AI Suggested Steps:</h6>
|
||||||
|
<div id="stepsContainer"></div>
|
||||||
|
</div>
|
||||||
<div class="mb-3 text-end">
|
<div class="mb-3 text-end">
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary" id="aiSuggestBtn">
|
<button type="button" class="btn btn-sm btn-outline-secondary" id="aiSuggestBtn">
|
||||||
<span id="aiSuggestSpinner" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
|
<span id="aiSuggestSpinner" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
|
||||||
@ -290,6 +294,17 @@ $project_image_url = htmlspecialchars($_SERVER['PROJECT_IMAGE_URL'] ?? '');
|
|||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
function htmlspecialchars(str) {
|
||||||
|
var map = {
|
||||||
|
'&': '&',
|
||||||
|
'<': '<',
|
||||||
|
'>': '>',
|
||||||
|
'"': '"',
|
||||||
|
''': '''
|
||||||
|
};
|
||||||
|
return str.replace(/[&<>'"']/g, function(m) { return map[m]; });
|
||||||
|
}
|
||||||
|
|
||||||
// Client-side validation for the Add Process form
|
// Client-side validation for the Add Process form
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
var form = document.getElementById('addProcessForm');
|
var form = document.getElementById('addProcessForm');
|
||||||
@ -329,6 +344,32 @@ $project_image_url = htmlspecialchars($_SERVER['PROJECT_IMAGE_URL'] ?? '');
|
|||||||
if (data.success) {
|
if (data.success) {
|
||||||
processNameInput.value = data.data.name;
|
processNameInput.value = data.data.name;
|
||||||
processDescriptionInput.value = data.data.description;
|
processDescriptionInput.value = data.data.description;
|
||||||
|
|
||||||
|
const aiGeneratedStepsDiv = document.getElementById('aiGeneratedSteps');
|
||||||
|
const stepsContainer = document.getElementById('stepsContainer');
|
||||||
|
stepsContainer.innerHTML = ''; // Clear previous steps
|
||||||
|
|
||||||
|
if (data.data.steps && data.data.steps.length > 0) {
|
||||||
|
aiGeneratedStepsDiv.style.display = 'block';
|
||||||
|
data.data.steps.forEach((step, index) => {
|
||||||
|
const stepHtml = `
|
||||||
|
<div class="mb-3 p-2 border rounded bg-light">
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="stepTitle_${index}" class="form-label fw-bold">Step ${index + 1} Title</label>
|
||||||
|
<input type="text" class="form-control" id="stepTitle_${index}" name="steps[${index}][title]" value="${htmlspecialchars(step.title)}" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="stepDescription_${index}" class="form-label">Step ${index + 1} Description</label>
|
||||||
|
<textarea class="form-control" id="stepDescription_${index}" name="steps[${index}][description]" rows="2" required>${htmlspecialchars(step.description)}</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
stepsContainer.insertAdjacentHTML('beforeend', stepHtml);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
aiGeneratedStepsDiv.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
messageArea.innerHTML = '<div class="alert alert-success alert-dismissible fade show" role="alert">AI suggestion generated successfully!<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
|
messageArea.innerHTML = '<div class="alert alert-success alert-dismissible fade show" role="alert">AI suggestion generated successfully!<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
|
||||||
} else {
|
} else {
|
||||||
messageArea.innerHTML = `<div class="alert alert-danger alert-dismissible fade show" role="alert">Failed to get AI suggestion: ${data.error}<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>`;
|
messageArea.innerHTML = `<div class="alert alert-danger alert-dismissible fade show" role="alert">Failed to get AI suggestion: ${data.error}<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>`;
|
||||||
|
|||||||
@ -15,6 +15,53 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||||
|
|
||||||
if ($stmt->execute()) {
|
if ($stmt->execute()) {
|
||||||
|
// Handle process steps
|
||||||
|
$submitted_steps = $_POST['steps'] ?? [];
|
||||||
|
$existing_step_ids = [];
|
||||||
|
|
||||||
|
// Fetch existing steps to identify deletions
|
||||||
|
$stmt_fetch_existing = $pdo->prepare("SELECT id FROM process_steps WHERE process_id = :process_id");
|
||||||
|
$stmt_fetch_existing->bindParam(':process_id', $id, PDO::PARAM_INT);
|
||||||
|
$stmt_fetch_existing->execute();
|
||||||
|
$db_existing_step_ids = $stmt_fetch_existing->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
$steps_to_keep_ids = [];
|
||||||
|
|
||||||
|
foreach ($submitted_steps as $order => $step) {
|
||||||
|
$step_id = $step['id'] ?? null;
|
||||||
|
$step_title = $step['title'] ?? '';
|
||||||
|
$step_description = $step['description'] ?? '';
|
||||||
|
|
||||||
|
if (!empty($step_title) && !empty($step_description)) {
|
||||||
|
if ($step_id) {
|
||||||
|
// Update existing step
|
||||||
|
$stmt_update_step = $pdo->prepare("UPDATE process_steps SET title = :title, description = :description, step_order = :step_order WHERE id = :id AND process_id = :process_id");
|
||||||
|
$stmt_update_step->bindParam(':title', $step_title, PDO::PARAM_STR);
|
||||||
|
$stmt_update_step->bindParam(':description', $step_description, PDO::PARAM_STR);
|
||||||
|
$stmt_update_step->bindParam(':step_order', $order, PDO::PARAM_INT);
|
||||||
|
$stmt_update_step->bindParam(':id', $step_id, PDO::PARAM_INT);
|
||||||
|
$stmt_update_step->bindParam(':process_id', $id, PDO::PARAM_INT);
|
||||||
|
$stmt_update_step->execute();
|
||||||
|
$steps_to_keep_ids[] = $step_id;
|
||||||
|
} else {
|
||||||
|
// Insert new step
|
||||||
|
$stmt_insert_step = $pdo->prepare("INSERT INTO process_steps (process_id, title, description, step_order) VALUES (:process_id, :title, :description, :step_order)");
|
||||||
|
$stmt_insert_step->bindParam(':process_id', $id, PDO::PARAM_INT);
|
||||||
|
$stmt_insert_step->bindParam(':title', $step_title, PDO::PARAM_STR);
|
||||||
|
$stmt_insert_step->bindParam(':description', $step_description, PDO::PARAM_STR);
|
||||||
|
$stmt_insert_step->bindParam(':step_order', $order, PDO::PARAM_INT);
|
||||||
|
$stmt_insert_step->execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete steps that were removed from the form
|
||||||
|
$steps_to_delete = array_diff($db_existing_step_ids, $steps_to_keep_ids);
|
||||||
|
if (!empty($steps_to_delete)) {
|
||||||
|
$placeholders = implode(',', array_fill(0, count($steps_to_delete), '?'));
|
||||||
|
$stmt_delete_steps = $pdo->prepare("DELETE FROM process_steps WHERE process_id = ? AND id IN ($placeholders)");
|
||||||
|
$stmt_delete_steps->execute(array_merge([$id], $steps_to_delete));
|
||||||
|
}
|
||||||
header('Location: index.php?success=processupdated');
|
header('Location: index.php?success=processupdated');
|
||||||
exit();
|
exit();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -13,7 +13,12 @@ if (isset($_GET['id']) && is_numeric($_GET['id'])) {
|
|||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$process = $stmt->fetch(PDO::FETCH_ASSOC);
|
$process = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if (!$process) {
|
if ($process) {
|
||||||
|
$stmt_steps = $pdo->prepare("SELECT title, description FROM process_steps WHERE process_id = :process_id ORDER BY step_order ASC");
|
||||||
|
$stmt_steps->bindParam(':process_id', $id, PDO::PARAM_INT);
|
||||||
|
$stmt_steps->execute();
|
||||||
|
$process['steps'] = $stmt_steps->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} else {
|
||||||
$error_message = 'Process not found.';
|
$error_message = 'Process not found.';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,6 +93,19 @@ $project_image_url = htmlspecialchars($_SERVER['PROJECT_IMAGE_URL'] ?? '');
|
|||||||
<dt class="col-sm-3">Created At:</dt>
|
<dt class="col-sm-3">Created At:</dt>
|
||||||
<dd class="col-sm-9"><?php echo date("M d, Y H:i:s", strtotime($process['created_at'])); ?></dd>
|
<dd class="col-sm-9"><?php echo date("M d, Y H:i:s", strtotime($process['created_at'])); ?></dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
|
<?php if (!empty($process['steps'])): ?>
|
||||||
|
<h5 class="mt-4">Process Steps:</h5>
|
||||||
|
<ul class="list-group mb-4">
|
||||||
|
<?php foreach ($process['steps'] as $index => $step): ?>
|
||||||
|
<li class="list-group-item">
|
||||||
|
<h6 class="mb-1">Step <?php echo $index + 1; ?>: <?php echo htmlspecialchars($step['title']); ?></h6>
|
||||||
|
<p class="mb-0 text-muted"><?php echo nl2br(htmlspecialchars($step['description'])); ?></p>
|
||||||
|
</li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="mt-4 text-end">
|
<div class="mt-4 text-end">
|
||||||
<a href="edit_process.php?id=<?php echo $process['id']; ?>" class="btn btn-primary me-2">
|
<a href="edit_process.php?id=<?php echo $process['id']; ?>" class="btn btn-primary me-2">
|
||||||
<i data-feather="edit" style="width: 16px; height: 16px;"></i> Edit Process
|
<i data-feather="edit" style="width: 16px; height: 16px;"></i> Edit Process
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user