Deploy: 2026-05-22 00:12:07
This commit is contained in:
@@ -70,4 +70,47 @@ class GroupController extends BaseController
|
||||
|
||||
$response->json(['status' => 'success', 'message' => 'Contact added to group']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach multiple contacts to a group in bulk
|
||||
*/
|
||||
public function bulkAddContacts(Request $request, Response $response)
|
||||
{
|
||||
$errors = $this->validate($request, [
|
||||
'group_id' => 'required',
|
||||
'contact_ids' => 'required'
|
||||
]);
|
||||
if (!empty($errors)) {
|
||||
$response->status(400)->json(['status' => 'error', 'errors' => $errors]);
|
||||
return;
|
||||
}
|
||||
|
||||
$body = $request->getBody();
|
||||
$groupId = (int)$body['group_id'];
|
||||
$contactIds = $body['contact_ids'];
|
||||
|
||||
if (!is_array($contactIds)) {
|
||||
$response->status(400)->json(['status' => 'error', 'message' => 'contact_ids must be an array']);
|
||||
return;
|
||||
}
|
||||
|
||||
$groupModel = new ContactGroup();
|
||||
|
||||
$pdo = \App\Core\Database::getConnection();
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
foreach ($contactIds as $contactId) {
|
||||
$groupModel->attachContact($groupId, (int)$contactId);
|
||||
}
|
||||
$pdo->commit();
|
||||
} catch (\Exception $e) {
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
$response->status(500)->json(['status' => 'error', 'message' => 'Bulk insert failed: ' . $e->getMessage()]);
|
||||
return;
|
||||
}
|
||||
|
||||
$response->json(['status' => 'success', 'message' => 'Contacts added to group in bulk']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -713,7 +713,7 @@
|
||||
<button class="nav-item" :class="{ 'active': activeDashboardTab === 'whatsapp' }" @click="activeDashboardTab = 'whatsapp'" id="nav-whatsapp-btn">
|
||||
<span>📱</span> WhatsApp Connection
|
||||
</button>
|
||||
<button class="nav-item" :class="{ 'active': activeDashboardTab === 'contacts' }" @click="activeDashboardTab = 'contacts'; fetchContacts()" id="nav-contacts-btn">
|
||||
<button class="nav-item" :class="{ 'active': activeDashboardTab === 'contacts' }" @click="activeDashboardTab = 'contacts'; fetchContacts(); fetchGroups()" id="nav-contacts-btn">
|
||||
<span>👥</span> Contacts Directory
|
||||
</button>
|
||||
<button class="nav-item" :class="{ 'active': activeDashboardTab === 'templates' }" @click="activeDashboardTab = 'templates'; fetchTemplates()" id="nav-templates-btn">
|
||||
@@ -846,10 +846,31 @@
|
||||
<button class="btn btn-primary" style="width: auto; padding: 0.5rem 1rem; font-size: 0.85rem;" @click="openAddContactModal()" id="btn-add-contact">+ Add Contact</button>
|
||||
</div>
|
||||
|
||||
<!-- Bulk Action Bar -->
|
||||
<div x-show="selectedContactIds.length > 0" class="bulk-action-bar" style="background: rgba(6, 182, 212, 0.1); border: 1px solid var(--primary-accent); padding: 1rem; border-radius: 10px; margin-bottom: 1.5rem; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 1rem; animation: fadeIn 0.3s ease-out;">
|
||||
<span style="font-size: 0.9rem; color: var(--text-primary); font-weight: 600;">
|
||||
Selected: <span x-text="selectedContactIds.length" style="color: var(--primary-accent);"></span> contact(s)
|
||||
</span>
|
||||
<div style="display: flex; gap: 0.75rem; align-items: center; flex-wrap: wrap;">
|
||||
<select class="form-input" style="width: auto; padding: 0.5rem 1rem; font-size: 0.85rem;" x-model="bulkGroupId" @focus="fetchGroups()">
|
||||
<option value="">-- Add to Existing Group --</option>
|
||||
<template x-for="grp in groups" :key="grp.id">
|
||||
<option :value="grp.id" x-text="grp.name"></option>
|
||||
</template>
|
||||
</select>
|
||||
<span style="color: var(--text-secondary); font-size: 0.85rem;">or</span>
|
||||
<input type="text" class="form-input" style="width: 180px; padding: 0.5rem 1rem; font-size: 0.85rem;" placeholder="New Group Name" x-model="bulkNewGroupName">
|
||||
<button class="btn btn-primary" style="width: auto; padding: 0.5rem 1rem; font-size: 0.85rem;" @click="applyBulkGroup()">Apply Grouping</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="data-table-container">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 40px; text-align: center;">
|
||||
<input type="checkbox" @change="selectedContactIds = $event.target.checked ? contacts.map(c => c.id) : []" :checked="contacts.length > 0 && selectedContactIds.length === contacts.length" style="cursor: pointer; width: 16px; height: 16px;">
|
||||
</th>
|
||||
<th>Name</th>
|
||||
<th>Phone</th>
|
||||
<th>Email</th>
|
||||
@@ -859,6 +880,9 @@
|
||||
<tbody>
|
||||
<template x-for="contact in contacts" :key="contact.id">
|
||||
<tr>
|
||||
<td style="text-align: center;">
|
||||
<input type="checkbox" :value="contact.id" x-model="selectedContactIds" style="cursor: pointer; width: 16px; height: 16px;">
|
||||
</td>
|
||||
<td class="font-semibold" x-text="contact.name"></td>
|
||||
<td x-text="contact.phone || 'N/A'"></td>
|
||||
<td x-text="contact.email || 'N/A'"></td>
|
||||
@@ -1154,6 +1178,9 @@
|
||||
activeDashboardTab: 'whatsapp',
|
||||
whatsappSession: null,
|
||||
contacts: [],
|
||||
selectedContactIds: [],
|
||||
bulkGroupId: '',
|
||||
bulkNewGroupName: '',
|
||||
templates: [],
|
||||
campaigns: [],
|
||||
pollingIntervalId: null,
|
||||
@@ -1397,6 +1424,9 @@
|
||||
|
||||
// CRM List Fetchers
|
||||
async fetchContacts() {
|
||||
this.selectedContactIds = [];
|
||||
this.bulkGroupId = '';
|
||||
this.bulkNewGroupName = '';
|
||||
try {
|
||||
const response = await fetch('/api/contacts', {
|
||||
headers: { 'Authorization': `Bearer ${this.token}` }
|
||||
@@ -1410,6 +1440,67 @@
|
||||
}
|
||||
},
|
||||
|
||||
async applyBulkGroup() {
|
||||
if (this.selectedContactIds.length === 0) {
|
||||
alert('Please select at least one contact first.');
|
||||
return;
|
||||
}
|
||||
if (!this.bulkGroupId && !this.bulkNewGroupName.trim()) {
|
||||
alert('Please select an existing group or type a new group name.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.actionLoading = true;
|
||||
try {
|
||||
let groupId = this.bulkGroupId;
|
||||
|
||||
// If a new group name is typed, create the group first
|
||||
if (this.bulkNewGroupName.trim()) {
|
||||
const newGroupRes = await fetch('/api/groups', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${this.token}`
|
||||
},
|
||||
body: JSON.stringify({ name: this.bulkNewGroupName.trim() })
|
||||
});
|
||||
const newGroupData = await newGroupRes.json();
|
||||
if (!newGroupRes.ok) {
|
||||
throw new Error(newGroupData.message || newGroupData.error || 'Failed to create group');
|
||||
}
|
||||
groupId = newGroupData.id;
|
||||
}
|
||||
|
||||
// Attach selected contacts in bulk
|
||||
const bulkRes = await fetch('/api/groups/bulk-add', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${this.token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
group_id: groupId,
|
||||
contact_ids: this.selectedContactIds
|
||||
})
|
||||
});
|
||||
const bulkData = await bulkRes.json();
|
||||
if (bulkRes.ok) {
|
||||
alert('Successfully added selected contacts to the group!');
|
||||
this.selectedContactIds = [];
|
||||
this.bulkGroupId = '';
|
||||
this.bulkNewGroupName = '';
|
||||
await this.fetchGroups();
|
||||
} else {
|
||||
alert(bulkData.error || bulkData.message || 'Failed to apply grouping.');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error applying bulk grouping:', err);
|
||||
alert(err.message || 'An error occurred during bulk grouping.');
|
||||
} finally {
|
||||
this.actionLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async fetchTemplates() {
|
||||
try {
|
||||
const response = await fetch('/api/templates', {
|
||||
|
||||
@@ -56,6 +56,7 @@ $router->post('/api/contacts', [\App\Controllers\ContactController::class, '
|
||||
$router->get('/api/groups', [\App\Controllers\GroupController::class, 'index'], [\App\Middlewares\AuthMiddleware::class]);
|
||||
$router->post('/api/groups', [\App\Controllers\GroupController::class, 'store'], [\App\Middlewares\AuthMiddleware::class]);
|
||||
$router->post('/api/groups/add', [\App\Controllers\GroupController::class, 'addContact'], [\App\Middlewares\AuthMiddleware::class]);
|
||||
$router->post('/api/groups/bulk-add', [\App\Controllers\GroupController::class, 'bulkAddContacts'], [\App\Middlewares\AuthMiddleware::class]);
|
||||
|
||||
$router->get('/api/templates', [\App\Controllers\TemplateController::class, 'index'], [\App\Middlewares\AuthMiddleware::class]);
|
||||
$router->post('/api/templates', [\App\Controllers\TemplateController::class, 'store'], [\App\Middlewares\AuthMiddleware::class]);
|
||||
|
||||
Reference in New Issue
Block a user