nhance/app/Views/commission_rules_list.php

914 lines
31 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<style>
.container {
max-width: 1400px;
margin: 0 auto;
}
.action-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.filter-section {
display: flex;
align-items: center;
gap: 15px;
}
/* Card Grid */
.rules-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 25px;
}
.rule-card {
background: white;
border-radius: 10px;
padding: 10px;
box-shadow: 0 5px 20px rgba(0,0,0,0.1);
transition: all 0.3s ease;
/* border-top: 3px solid #E26728; */
border-top: 3px solid #02a8b5;
}
.rule-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
}
.rule-card-header {
display: flex;
justify-content: space-between;
align-items: start;
margin-bottom: 12px;
}
.rule-name {
font-size: 14px;
font-weight: bold;
color: #2c3e50;
margin-bottom: 4px;
line-height: 1.0;
}
.rule-details {
margin: 10px 0;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
line-height: 1.4;
}
.rule-actions {
display: flex;
gap: 8px;
margin-top: 12px;
padding-top: 12px;
/* border-top: 1px solid #e9ecef; */
justify-content: flex-end;
}
.department-badge {
padding: 2px 5px;
border-radius: 15px;
font-size: 0.75rem;
font-weight: 600;
color: white;
background: linear-gradient(45deg, #3498db, #2980b9);
}
.condition-badge {
padding: 2px 5px;
border-radius: 15px;
font-size: 7px;
color: white;
background: linear-gradient(45deg, #3498db, #2980b9);
}
.detail-item {
font-size: 10px;
color: #555;
}
.detail-label {
font-weight: 500;
color: #000000;
}
#ruleModal .modal-body {
max-height: 450px; /* adjust as needed */
overflow-y: auto;
}
.btn {
padding: 0px 10px !important;
border-radius: 18px !important;
}
.close {
color: white;
font-size: 2rem;
font-weight: bold;
cursor: pointer;
transition: transform 0.2s;
}
.close:hover {
transform: rotate(90deg);
}
.conditions-container {
border: 2px solid #e9ecef;
border-radius: 8px;
padding: 15px;
margin-bottom: 15px;
background: #f8f9fa;
}
.condition-row {
display: grid;
grid-template-columns: 1fr 1fr 1fr auto;
gap: 10px;
margin-bottom: 10px;
align-items: end;
}
.calculation-section {
background: #f8f9fa;
border: 2px solid #e9ecef;
border-radius: 8px;
padding: 15px;
}
.component-row {
display: grid;
grid-template-columns: 1fr 1fr 1fr auto;
gap: 10px;
margin-bottom: 10px;
align-items: end;
}
.alert {
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
font-weight: 600;
}
.alert-success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.alert-error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.hidden {
display: none;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: #7f8c8d;
}
.empty-state h3 {
font-size: 1.5rem;
margin-bottom: 10px;
}
@media (max-width: 768px) {
.rules-grid {
grid-template-columns: 1fr;
}
.condition-row, .component-row {
grid-template-columns: 1fr;
}
.action-bar {
flex-direction: column;
gap: 15px;
}
.filter-section {
width: 100%;
flex-direction: column;
}
.form-control {
width: 100%;
}
}
.readonly-select {
pointer-events: none;
/* background-color: #f0f0f0; */
background-color: #e0e0e0;
color: #666;
}
</style>
<div class="container">
<!-- Action Bar -->
<div class="action-bar">
<div class="filter-section">
<label for="filterDepartment">Filter:</label>
<select class="form-control" id="filterDepartment" onchange="filterRules()">
<option value="">All Departments</option>
<option value="Health">Health</option>
<option value="Motor">Motor</option>
<option value="Fire">Fire</option>
<option value="Travel">Travel</option>
<option value="Marine">Marine</option>
</select>
</div>
<button class="btn btn-primary" onclick="openModal()">Create New Rule</button>
</div>
<!-- Rules Grid -->
<div id="rulesList" class="rules-grid">
<!-- Rules will be displayed here as cards -->
</div>
</div>
<!-- Modal -->
<div id="ruleModal" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true" aria-modal="true" data-bs-backdrop="static">
<div class="modal-dialog modal-lg" style="max-width: 800px;">
<div class="modal-content">
<div class="modal-header" style="background-color: gainsboro;">
<h5 class="modal-title" id="modalTitle">Create New Rule <span id="heading"></span></h5>
<button type="button" class="close" data-dismiss="modal" aria-hidden="true" onclick="closeModal()">×</button>
</div>
<div class="modal-body">
<div id="alert-container"></div>
<form id="ruleForm">
<input type="hidden" id="ruleId" name="rule_id">
<div class="form-group">
<label for="department">Department: *</label>
<select class="form-control readonly-select" id="department" name="department" required>
<?php
if (isset($departments) && count($departments)) {
foreach ($departments as $key => $value) {
echo "<option value=" . $key . ">" . $value . "</option>";
}
}
?>
</select>
</div>
<div class="form-group">
<label for="ruleName">Rule Name: *</label>
<input type="text" class="form-control" id="ruleName" name="rule_name" required placeholder="Enter rule name">
</div>
<!-- Conditions Section -->
<div class="form-group">
<label>Conditions:</label>
<div class="conditions-container">
<div id="conditionsContainer">
<!-- Conditions will be added dynamically -->
</div>
<button type="button" class="btn btn-primary btn-small" onclick="addCondition()">+ Add Condition</button>
</div>
</div>
<!-- Calculation Section -->
<div class="form-group">
<label>Calculation:</label>
<div class="calculation-section">
<div class="form-group">
<label for="calculationType">Calculation Type:</label>
<select class="form-control" id="calculationType" name="calculation_type" onchange="toggleCalculationFields()">
<option value="percentage">Percentage</option>
<option value="composite">Composite</option>
<option value="fixed">Fixed Amount</option>
</select>
</div>
<div id="simpleCalculation">
<div class="form-group">
<label for="calculationValue">Value:</label>
<input type="number" class="form-control" id="calculationValue" name="calculation_value" step="0.01">
</div>
<div class="form-group">
<label for="calculationOn">Applied On:</label>
<select class="form-control" id="calculationOn" name="calculation_on">
<option value="premium">Premium</option>
<option value="net_premium">Net Premium</option>
<option value="od_premium">OD Premium</option>
<option value="tp_premium">TP Premium</option>
</select>
</div>
</div>
<div id="compositeCalculation" class="hidden">
<div id="componentsContainer">
<!-- Composite components will be added here -->
</div>
<button type="button" class="btn btn-primary btn-small" onclick="addComponent()">+ Add Component</button>
</div>
</div>
</div>
<div class="form-group" style="display: flex; gap: 10px;">
<button type="submit" class="btn btn-success" id="save_rule_btn">Save Rule</button>
<button type="button" class="btn btn-secondary" id="rest_rule_btn" onclick="resetForm()">Reset Form</button>
<small class="text-danger" id="used_text"></small>
</div>
</form>
</div>
</div>
</div>
</div>
<script>
let commission_file_id = '<?= $commission_file_id ?>'
let rules = '<?= isset($rules) && !empty($rules) ? json_encode($rules) : '' ?>';
// Field options based on department
const departmentFields = JSON.parse('<?= $departmentFields ?>');
let conditionCount = 0;
let componentCount = 0;
// Initialize the page
document.addEventListener('DOMContentLoaded', function() {
// loadAllRules();
addCondition(); // Add initial condition
});
function addCondition() {
conditionCount++;
const container = document.getElementById('conditionsContainer');
const department = document.getElementById('department').value;
const conditionDiv = document.createElement('div');
conditionDiv.className = 'condition-row';
conditionDiv.id = `condition-${conditionCount}`;
let fieldOptions = '<option value="">Select Field</option>';
if (department && departmentFields[department]) {
departmentFields[department].forEach(field => {
fieldOptions += `<option value="${field}">${field.replace('_', ' ').toUpperCase()}</option>`;
});
}
conditionDiv.innerHTML = `
<div>
<select class="form-control" name="condition_field[]" required>
${fieldOptions}
</select>
</div>
<div>
<select class="form-control" name="condition_operator[]" required>
<option value="">Select Operator</option>
<option value="==">==(equals)</option>
<option value="!=">=!=(not equals)</option>
<option value=">">>(greater than)</option>
<option value="<"><(less than)</option>
<option value=">=">>=(greater than or equal)</option>
<option value="<=">>=(less than or equal)</option>
<option value="between">between</option>
<option value="in">in (array)</option>
</select>
</div>
<div>
<input type="text" class="form-control" name="condition_value[]" placeholder="Value" required>
</div>
<div>
<button type="button" class="btn btn-danger btn-small" onclick="removeCondition('condition-${conditionCount}')">×</button>
</div>
`;
container.appendChild(conditionDiv);
}
function removeCondition(conditionId) {
const element = document.getElementById(conditionId);
if (element) {
element.remove();
}
}
function addComponent() {
componentCount++;
const container = document.getElementById('componentsContainer');
const componentDiv = document.createElement('div');
componentDiv.className = 'component-row';
componentDiv.id = `component-${componentCount}`;
componentDiv.innerHTML = `
<div>
<input type="number" class="form-control" name="component_percentage[]" placeholder="Percentage" step="0.01" required>
</div>
<div>
<select class="form-control" name="component_on[]" required>
<option value="premium">Premium</option>
<option value="net_premium">Net Premium</option>
<option value="od_premium">OD Premium</option>
<option value="tp_premium">TP Premium</option>
</select>
</div>
<div>
<label><input type="checkbox" name="only_first_year[]" value="1"> Only First Year</label>
</div>
<div>
<button type="button" class="btn btn-danger btn-small" onclick="removeComponent('component-${componentCount}')">×</button>
</div>
`;
container.appendChild(componentDiv);
}
function removeComponent(componentId) {
const element = document.getElementById(componentId);
if (element) {
element.remove();
}
}
function toggleCalculationFields() {
const type = document.getElementById('calculationType').value;
const simple = document.getElementById('simpleCalculation');
const composite = document.getElementById('compositeCalculation');
if (type === 'composite') {
simple.classList.add('hidden');
composite.classList.remove('hidden');
if (componentCount === 0) {
addComponent();
}
} else {
simple.classList.remove('hidden');
composite.classList.add('hidden');
}
}
// Update field options when department changes
document.getElementById('department').addEventListener('change', function() {
const conditions = document.querySelectorAll('[name="condition_field[]"]');
const department = this.value;
conditions.forEach(select => {
const currentValue = select.value;
select.innerHTML = '<option value="">Select Field</option>';
if (department && departmentFields[department]) {
departmentFields[department].forEach(field => {
const option = document.createElement('option');
option.value = field;
option.textContent = field.replace('_', ' ').toUpperCase();
if (field === currentValue) {
option.selected = true;
}
select.appendChild(option);
});
}
});
});
// Form submission
document.getElementById('ruleForm').addEventListener('submit', function(e) {
e.preventDefault();
const rule_id = document.getElementById('ruleId').value || null;
const formData = new FormData(this);
const ruleData = {
id: document.getElementById('ruleId').value || null,
name: formData.get('rule_name'),
department: formData.get('department'),
conditions: [],
calculation: {}
};
// Process conditions
const fields = formData.getAll('condition_field[]');
const operators = formData.getAll('condition_operator[]');
const values = formData.getAll('condition_value[]');
for (let i = 0; i < fields.length; i++) {
if (fields[i] && operators[i] && values[i]) {
let value = values[i];
// Parse value based on operator
if (operators[i] === 'between') {
value = value.split(',').map(v => parseFloat(v.trim()));
} else if (operators[i] === 'in') {
value = value.split(',').map(v => v.trim());
} else if (!isNaN(value)) {
value = parseFloat(value);
} else if (value.toLowerCase() === 'true') {
value = true;
} else if (value.toLowerCase() === 'false') {
value = false;
}
ruleData.conditions.push({
field: fields[i],
operator: operators[i],
value: value
});
}
}
// Process calculation
const calculationType = formData.get('calculation_type');
if (calculationType === 'composite') {
const percentages = formData.getAll('component_percentage[]');
const ons = formData.getAll('component_on[]');
const firstYears = formData.getAll('only_first_year[]');
ruleData.calculation = {
type: 'composite',
components: []
};
for (let i = 0; i < percentages.length; i++) {
if (percentages[i] && ons[i]) {
const component = {
percentage: parseFloat(percentages[i]),
on: ons[i]
};
if (firstYears.includes('1')) {
component.only_first_year = true;
}
ruleData.calculation.components.push(component);
}
}
} else {
ruleData.calculation = {
type: calculationType,
value: parseFloat(formData.get('calculation_value')),
on: formData.get('calculation_on')
};
}
console.log('', ruleData);
console.log(formData.get('calculation_value'));
console.log($('#calculation_value').val());
console.log(parseFloat(formData.get('calculation_value')));
let url = '<?= base_url('commission/rules/save') ?>';
// Data to send in the AJAX request
let requestData = {
file_id: commission_file_id,
rule_id: rule_id,
rule_data: ruleData,
};
$('.loader').fadeIn();
$('.loader-mask').fadeIn();
// Send AJAX request
sendAjaxRequestForGlobal(url, 'POST', requestData, function(response) {
console.log('Data fetched successfully:', response);
console.log('response.data type', typeof response.data);
console.log('rules type', typeof rules);
if (response.status == true) {
toastr.success(response.message, 'SUCCESS');
// rules = response.data;
displayRules(response.data);
}else{
toastr.warning(response.message, 'WARNING');
}
$('.loader').fadeOut();
$('.loader-mask').delay(350).fadeOut('slow');
resetForm()
$('.close').click();
}, function(xhr, status, error) {
$('.loader').fadeOut();
$('.loader-mask').delay(350).fadeOut('slow');
console.error('Error fetching data:', error);
console.error(xhr.responseText);
toastr.error('An error occurred while fetching the data.', 'ERROR');
});
});
function resetForm() {
document.getElementById('ruleForm').reset();
document.getElementById('ruleId').value = '';
document.getElementById('conditionsContainer').innerHTML = '';
document.getElementById('componentsContainer').innerHTML = '';
conditionCount = 0;
componentCount = 0;
addCondition();
toggleCalculationFields();
}
function openModal() {
$('#modalTitle').text('Create New Rule');
var myModal = new bootstrap.Modal(document.getElementById('ruleModal'));
myModal.show();
resetForm();
}
function closeModal() {
document.getElementById('ruleModal').style.display = 'none';
}
function editRule(id) {
checkRuleUsed('edit', id);
// Convert rules JSON to array (ONLY ONCE)
if (typeof rules === "string") {
rules = JSON.parse(rules);
}
const rule = rules.find(r => r.id == id);
if (!rule) return;
console.log({ rule });
$('#modalTitle').text('Edit Rule');
$('#ruleId').val(rule.id);
$('#department').val(rule.department).trigger('change');
$('#ruleName').val(rule.name);
// Clear existing conditions
$('#conditionsContainer').html('');
conditionCount = 0;
// Add conditions
rule.conditions.forEach(condition => {
addCondition();
const lastCondition = $('#conditionsContainer .condition-row:last');
lastCondition.find('[name="condition_field[]"]').val(condition.field);
lastCondition.find('[name="condition_operator[]"]').val(condition.operator);
lastCondition.find('[name="condition_value[]"]').val(
Array.isArray(condition.value) ? condition.value.join(', ') : condition.value
);
});
// Set calculation type
$('#calculationType').val(rule.calculation.type);
toggleCalculationFields();
// If composite calculation
if (rule.calculation.type === 'composite') {
$('#componentsContainer').html('');
componentCount = 0;
rule.calculation.components.forEach(component => {
addComponent();
const lastComponent = $('#componentsContainer .component-row:last');
lastComponent.find('[name="component_percentage[]"]').val(component.percentage);
lastComponent.find('[name="component_on[]"]').val(component.on);
if (component.only_first_year) {
lastComponent.find('[name="only_first_year[]"]').prop('checked', true);
}
});
} else {
$('#calculationValue').val(rule.calculation.value);
$('#calculationOn').val(rule.calculation.on);
}
var myModal = new bootstrap.Modal(document.getElementById('ruleModal'));
myModal.show();
}
function deleteRule(id) {
Swal.fire({
icon: "warning",
title: "Are you sure you want to delete this rule?",
showCancelButton: true, // For Cancel
confirmButtonText: "Yes",
cancelButtonText: "Cancel",
confirmButtonColor: "#ff3333"
}).then((result) => {
if (result.isConfirmed) {
let url = '<?= base_url('commission/rules/remove') ?>';
// Data to send in the AJAX request
let requestData = {
file_id: commission_file_id,
rule_id: id,
is_deleted: true,
};
$('.loader').fadeIn();
$('.loader-mask').fadeIn();
// Send AJAX request
sendAjaxRequestForGlobal(url, 'POST', requestData, function(response) {
console.log('Data fetched successfully:', response);
console.log('response.data type', typeof response.data);
console.log('rules type', typeof rules);
if (response.status == true) {
toastr.success(response.message, 'SUCCESS');
// rules = response.data;
displayRules(response.data);
}else{
toastr.warning(response.message, 'WARNING');
}
$('.loader').fadeOut();
$('.loader-mask').delay(350).fadeOut('slow');
resetForm()
$('.close').click();
}, function(xhr, status, error) {
$('.loader').fadeOut();
$('.loader-mask').delay(350).fadeOut('slow');
console.error('Error fetching data:', error);
console.error(xhr.responseText);
toastr.error('An error occurred while fetching the data.', 'ERROR');
});
}
});
}
function filterRules() {
displayRules();
}
function displayRules(sucess_data = null) {
const filter = document.getElementById('filterDepartment').value;
const container = document.getElementById('rulesList');
let filteredRules = sucess_data ?? rules;
// console.log('filteredRules', filteredRules);
if(filteredRules == ""){
container.innerHTML = `
<div class="empty-state" style="grid-column: 1/-1;">
<h3>📋 No rules found</h3>
</div>
`;
return;
}
if(sucess_data == null){
filteredRules = JSON.parse(filteredRules);
// console.log('filteredRules', filteredRules);
}
if (filter) {
filteredRules = rules.filter(r => r.department === filter);
}
if (filteredRules.length === 0) {
container.innerHTML = `
<div class="empty-state" style="grid-column: 1/-1;">
<h3>📋 No rules found</h3>
<p>Create your first rule to get started!</p>
</div>
`;
return;
}
container.innerHTML = filteredRules.map(rule => {
// Build condition badges
const conditionBadges = rule.conditions
.map(c => `<span class="condition-badge">${c.field}</span>`)
.join(' ');
return `
<div class="rule-card">
<div class="rule-card-header">
<div>
<div class="rule-name">${rule.name}</div>
</div>
<span class="department-badge">${rule.department.charAt(0).toUpperCase() + rule.department.slice(1)}</span>
</div>
<div class="rule-details">
<div class="detail-item">
<span class="detail-label">Calculation Type:</span> ${rule.calculation.type}
</div>
<div class="detail-item">
<span class="detail-label">Value:</span> ${rule.calculation.value || "N/A"}${rule.calculation.type === 'percentage' ? '%' : ''}
</div>
<div class="detail-item">
<span class="detail-label">Applied On:</span> ${rule.calculation.on || 'N/A'}
</div>
<div class="detail-item">
<span class="detail-label">Conditions:</span> ${rule.conditions.length} condition(s)
</div>
</div>
${conditionBadges}
${rule.is_deleted
? `<div class="rule-actions">
<button class="btn btn-danger btn-small" disabled>Deleted</button>
</div>`
: `<div class="rule-actions">
<button class="btn btn-primary btn-small" onclick="editRule('${rule.id}')">
<i class="mdi mdi-pencil"></i>
</button>
<button class="btn btn-danger btn-small" onclick=" checkRuleUsed('delete','${rule.id}')">
<i class="mdi mdi-delete"></i>
</button>
</div>`
}
</div>
`;
}).join('');
}
function checkRuleUsed(type, rule_id){
let url = '<?= base_url('commission/checkRuleUsage') ?>';
// Data to send in the AJAX request
let requestData = {
rule_id: rule_id,
};
// Send AJAX request
sendAjaxRequestForGlobal(url, 'GET', requestData, function(response) {
console.log('Data fetched successfully:', response);
if (response.status == true) {
if(type == "edit"){
if (response.count > 0) {
$('#save_rule_btn').prop('disabled', true);
$('#rest_rule_btn').prop('disabled', true);
$('#used_text').text('This rule is already used and cannot be edited.');
} else {
$('#save_rule_btn').prop('disabled', false);
$('#rest_rule_btn').prop('disabled', false);
$('#used_text').text('');
}
}else{
if (response.count > 0) {
Swal.fire({
icon: "warning",
title: "This rule is already used and cannot be deleted.",
})
}else{
deleteRule(rule_id);
}
}
}
}, function(xhr, status, error) {
console.error('Error fetching data:', error);
console.error(xhr.responseText);
toastr.error('An error occurred while fetching the data.', 'ERROR');
});
}
// Initial display
displayRules();
</script>