DONE dropzone upload

This commit is contained in:
Thanakorn S
2026-02-25 17:21:50 +07:00
parent 8625652fe7
commit 1843308870
19 changed files with 331 additions and 53 deletions
+22
View File
@@ -0,0 +1,22 @@
<?php
session_start();
require '../../../preset.php';
require '../../../assets/utils/db_auth.php';
$sql = "SELECT *
FROM md_contact
WHERE
company_id = {$company_id}";
$sth = $pdo2->prepare($sql);
$sth->execute();
if ($sth->errorInfo()[0] != "00000" && !empty($sth->errorInfo()[0])) {
$answer["message"] = (empty($sth->errorInfo()[2])) ? $sth->errorInfo()[0] : $sth->errorInfo()[2];
exit(json_encode($answer));
}
$answer["output"] = $sth->fetchAll(PDO::FETCH_ASSOC);
$answer["success"] = 1;
exit(json_encode($answer));
?>
+170 -3
View File
@@ -6,9 +6,176 @@
$id = (int)$data['id'];
$answer["testA"] = $data;
$answer["testB"] = $_FILES;
exit(json_encode($answer));
/** * FILE SYNCHRONIZATION & UPLOAD LOGIC
* -----------------------------------------------------------------------------------
* Strategy: "Keep & Sync"
* 1. Identify which existing files were removed by the user and delete them from disk.
* 2. Upload any brand-new files dropped into the Dropzone.
* 3. Re-assemble a final comma-separated string containing (Kept Files + New Files).
* -----------------------------------------------------------------------------------
*/
// Define the physical path for storage
$target_dir = $include_url . "uploads/contact/";
/** 1. DISK CLEANUP (Removal of deleted files) **/
if ($id > 0) {
// Fetch currently stored filenames from DB for comparison
$sql_check = "SELECT `files` FROM md_contact WHERE company_id = :company_id AND id = :id";
$sth_check = $pdo2->prepare($sql_check);
$sth_check->execute([":company_id" => $company_id, ":id" => $id]);
$existing_db_string = $sth_check->fetchColumn();
if (!empty($existing_db_string)) {
$existing_files_array = explode(',', $existing_db_string);
// 'keep_files' comes from JS (Dropzone.files names currently in the UI)
$keep_files = isset($data['keep_files']) ? explode(',', $data['keep_files']) : [];
foreach ($existing_files_array as $file_on_disk) {
$file_on_disk = trim($file_on_disk);
if (empty($file_on_disk)) continue;
/**
* If a file exists in the database but is NOT in the 'keep_files' list,
* it means the user clicked 'Remove' in the UI. We delete it from the server.
*/
if (!in_array($file_on_disk, $keep_files)) {
$full_path = $target_dir . $file_on_disk;
if (file_exists($full_path)) {
unlink($full_path); // Physically remove (rm) the file
}
}
}
}
}
/** 2. PERMISSION & DIRECTORY CHECK **/
$parent_dir = dirname($target_dir);
if (!is_writable($parent_dir)) {
exit(json_encode([
"success" => 0,
"message" => "Server error: Target directory is not writable. Check permissions."
]));
}
// Ensure the directory exists
if (!is_dir($target_dir)) {
mkdir($target_dir, 0755, true);
}
/** 3. PROCESSING NEW UPLOADS **/
$uploaded_names = [];
if (isset($_FILES['contact_files'])) {
foreach ($_FILES['contact_files']['name'] as $key => $name) {
if ($_FILES['contact_files']['error'][$key] === UPLOAD_ERR_OK) {
$tmp_name = $_FILES['contact_files']['tmp_name'][$key];
$extension = pathinfo($name, PATHINFO_EXTENSION);
// Assign a unique ID to prevent filename collisions on the server
$file_id = uniqid() . "_" . time() . "." . $extension;
$destination = $target_dir . $file_id;
if (move_uploaded_file($tmp_name, $destination)) {
$uploaded_names[] = $file_id;
// Standard readable permission
chmod($destination, 0644);
} else {
error_log("Failed to move uploaded file: " . $name);
}
}
}
}
/** 4. CONSTRUCTING THE FINAL DATABASE STRING **/
// Parse files the user kept from the existing session
$keep_files = isset($data['keep_files']) ? explode(',', $data['keep_files']) : [];
// Merge old kept filenames with the brand-new unique filenames
$final_file_array = array_merge($keep_files, $uploaded_names);
// Clean up: Remove empty values and trim whitespace
$final_file_array = array_filter(array_map('trim', $final_file_array));
// The final comma-separated string for the DB 'files' column
$db_image_string = implode(",", $final_file_array);
/********************************************************************************** */
// 2. Initialize log array
$table_log = [];
// Get existing log and existing files if updating
if ($id > 0) {
$sql = "SELECT `log`, `files` FROM md_contact WHERE company_id = :company_id AND id = :id";
$sth = $pdo2->prepare($sql);
$sth->execute([
":company_id" => $company_id,
":id" => $id
]);
$row = $sth->fetch(PDO::FETCH_ASSOC);
if ($row) {
$table_log = json_decode($row['log'] ?? '[]', true);
}
}
// Append the new log entry
$table_log[] = $logging;
// 3. Database Operation
$params = [
":company_id" => $company_id,
":contact_name" => $data["contact_name"],
":tax_id" => $data["tax_id"],
":organization" => $data["organization"],
":branch" => $data["branch"],
":contact_type" => (int)$data["contact_type"],
":billing_address" => $data["billing_address"],
":shipping_location"=> $data["shipping_location"],
":shipping_address" => $data["shipping_address"],
":remark" => $data["remark"],
":files" => $db_image_string,
":status" => (int)$data["status"],
":log" => json_encode($table_log)
];
if ($id > 0) {
// UPDATE
$sql = "UPDATE md_contact SET
`contact_name` = :contact_name,
`tax_id` = :tax_id,
`organization` = :organization,
`branch` = :branch,
`contact_type` = :contact_type,
`billing_address` = :billing_address,
`shipping_location` = :shipping_location,
`shipping_address` = :shipping_address,
`remark` = :remark,
`files` = :files,
`status` = :status,
`log` = :log
WHERE id = :id AND company_id = :company_id";
$params[":id"] = $id;
$sth = $pdo2->prepare($sql);
$sth->execute($params);
} else {
// INSERT
$sql = "INSERT INTO md_contact
(company_id, contact_name, tax_id, organization, branch, contact_type,
billing_address, shipping_location, shipping_address, remark, files, status, log)
VALUES
(:company_id, :contact_name, :tax_id, :organization, :branch, :contact_type,
:billing_address, :shipping_location, :shipping_address, :remark, :files, :status, :log)";
$sth = $pdo2->prepare($sql);
$sth->execute($params);
}
$answer["success"] = 1;
exit(json_encode($answer));
@@ -0,0 +1,29 @@
<?php
session_start();
require '../../../preset.php';
require '../../../assets/utils/db_auth.php';
$id = (int)$data['id'];
$sql = "SELECT *
FROM md_contact
WHERE
company_id = {$company_id} and
id = {$id}";
$sth = $pdo2->prepare($sql);
$sth->execute();
if ($sth->errorInfo()[0] != "00000" && !empty($sth->errorInfo()[0])) {
$answer["message"] = (empty($sth->errorInfo()[2])) ? $sth->errorInfo()[0] : $sth->errorInfo()[2];
exit(json_encode($answer));
}
$target_dir = $include_url."uploads/contact/";
$answer["contact_dir"] = $target_dir;
$answer["output"] = $sth->fetch(PDO::FETCH_ASSOC);
$answer["success"] = 1;
exit(json_encode($answer));
?>
+27 -22
View File
@@ -89,7 +89,7 @@
<i class="ti ti-box-seam fs-4 "></i>
</div>
<div class="">
<span>Total Products</span>
<span>Total Contact</span>
<h2 class="fw-bold mb-0">356</h2>
</div>
@@ -135,7 +135,7 @@
<div class="col-lg-12 mb-5">
<div class="position-relative">
<input type="search" class="form-control ps-9" placeholder="Search by product name or SKU">
<input type="search" class="form-control ps-9" placeholder="Search by contact name">
<span class="position-absolute top-25 ms-4">
<i class="ti ti-search text-muted"></i>
@@ -186,13 +186,15 @@
</div>
<div class="table-responsive">
<table class="table table-hover mb-0 table-centered" id="product">
<table class="table table-hover mb-0 table-centered" id="contact">
<thead class="table-secondary border-light">
<tr>
<th>Product</th>
<th>SKU</th>
<th>Current Stock</th>
<th>Min Stock</th>
<th>Tax ID/Citizen ID</th>
<th>Name</th>
<th>Organization</th>
<th>Branch</th>
<th>Billing Address</th>
<th>Shipping Address</th>
<th>Status</th>
<th>Action</th>
</tr>
@@ -277,21 +279,22 @@
function retrieve_product() {
function retrieve_contact() {
return ajax_request({
url: "<?php echo $server_url?>inventory/api/engine/product.php",
url: "<?php echo $server_url?>contact/api/engine/contact.php",
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
// Register the array as a table name
alasql('CREATE TABLE IF NOT EXISTS product');
alasql.tables.product.data = res.output || [];
alasql('CREATE TABLE IF NOT EXISTS contact');
alasql.tables.contact.data = res.output || [];
var total_records = alasql.tables.product.data.length;
var total_records = alasql.tables.contact.data.length;
var table_id = 'product';
var table_id = 'contact';
var page = generate_pagination(table_id,total_records);
$(`table#${table_id} tfoot`).html(page);
@@ -308,37 +311,39 @@
}
function change_page_product(page_num) {
function change_page_contact(page_num) {
var offset = (page_num - 1) * prop_limit;
var page_data = alasql(`SELECT * FROM product LIMIT ${prop_limit} OFFSET ${offset}`);
var page_data = alasql(`SELECT * FROM contact LIMIT ${prop_limit} OFFSET ${offset}`);
var body = ``;
$.each(page_data, function(key, item) {
body += `<tr>
<td class="py-3">${item['product_name']}</td>
<td class="py-3">${item['sku']}</td>
<td class="py-3">${item['description']}</td>
<td class="py-3">${item['status']}</td>
<td class="py-3">${item['tax_id']}</td>
<td class="py-3">${item['contact_name']}</td>
<td class="py-3">${item['organization']}</td>
<td class="py-3">${item['branch']}</td>
<td class="py-3">${item['billing_address']}</td>
<td class="py-3">${item['shipping_address']}</td>
<td class="py-3">
${(item['status'])?'<span class="badge bg-success">Active</span>':'<span class="badge bg-secondary">Inactive</span>'}
</td>
<td class="py-3">
<a href="<?php echo $server_url?>inventory/manage_product.php?id=${item['id']}" class=""><i class="ti ti-eye fs-5"></i></a>
<a href="<?php echo $server_url?>contact/manage_contact.php?id=${item['id']}" class=""><i class="ti ti-eye fs-5"></i></a>
<a href="#" class="link-danger"><i class="ti ti-trash ms-2 fs-5"></i></a>
</td>
</tr>`;
})
$("table#product > tbody").html(body);
$("table#contact > tbody").html(body);
}
$(async function() {
try {
await retrieve_contact_type();
// await retrieve_product();
await retrieve_contact();
} catch (e) {
console.log(e);
}
+81 -27
View File
@@ -32,41 +32,36 @@
<h2 class="fs-4 mb-3">Contact Information</h2>
<div class="row g-5">
<div class="col-lg-6">
<label for="productName" class="form-label">Contact Name</label>
<input type="text" name="" id="productName" class="form-control" placeholder="Name">
<label for="contact_name" class="form-label">Contact Name</label>
<input type="text" name="" id="contact_name" class="form-control" placeholder="Name" required>
</div>
<div class="col-lg-6">
<label for="productName" class="form-label">Tax ID/Citizen ID</label>
<input type="text" name="" id="productName" class="form-control" placeholder="xxxxxxxxxxxxx">
<label for="tax_id" class="form-label">Tax ID/Citizen ID</label>
<input type="text" name="" id="tax_id" class="form-control" placeholder="xxxxxxxxxxxxx" required>
</div>
<div class="col-lg-6">
<label for="sku" class="form-label"> Organization</label>
<input type="text" name="" id="sku" class="form-control" placeholder="company name">
<label for="organization" class="form-label"> Organization</label>
<input type="text" name="" id="organization" class="form-control" placeholder="company name" required>
</div>
<div class="col-lg-6">
<label for="sku" class="form-label"> Branch</label>
<input type="text" name="" id="sku" class="form-control" placeholder="00000">
<label for="branch" class="form-label"> Branch</label>
<input type="text" name="" id="branch" class="form-control" placeholder="00000">
</div>
<div class="col-lg-6">
<label for="catogires" class="form-label"> Contact Type</label>
<select name="" id="catogires" class="form-select">
<option value="Leptop">Leptop</option>
<option value="Desktop">Desktop</option>
<option value="Accessories">Accessories</option>
<option value="Phone">Phone</option>
</select>
<label for="contact_type" class="form-label"> Contact Type</label>
<select name="" id="contact_type" class="form-select" required></select>
</div>
<div class="col-lg-6">
</div>
<div class="col-lg-12">
<label for="address" class="form-label"> Billing Address</label>
<textarea name="address" rows="3" id="address" class="form-control"
<label for="billing_address" class="form-label"> Billing Address</label>
<textarea name="billing_address" rows="3" id="billing_address" class="form-control"
placeholder="Customer Billing Address."></textarea>
</div>
@@ -155,13 +150,16 @@
let myDropzone;
$(document).ready(function() {
// Use the ID to initialize, ensuring NO conflict with theme classes
myDropzone = new Dropzone("#custom-dropzone", {
url: "<?php echo $server_url?>contact/api/engine/manage_contact.php",
autoProcessQueue: false,
uploadMultiple: true,
paramName: "contact_files"
});
// Use the ID to initialize, ensuring NO conflict with theme classes
myDropzone = new Dropzone("#custom-dropzone", {
url: "<?php echo $server_url?>contact/api/engine/manage_contact.php",
autoProcessQueue: false, // Important: Wait for the button click
uploadMultiple: true,
parallelUploads: 10,
acceptedFiles: "image/*",
addRemoveLinks: true,
paramName: "contact_files"
});
});
function manage_contact() {
@@ -174,6 +172,15 @@
formData.append('contact_files[]', file);
})
let keepFiles = [];
myDropzone.files.forEach(function(file) {
// If the file doesn't have a status or is already 'success', it's an existing file
if (file.status === undefined || file.status === Dropzone.SUCCESS || file.status === Dropzone.ADDED) {
keepFiles.push(file.name);
}
});
formData.append('keep_files', keepFiles.join(','));
return ajax_request({
url: "<?php echo $server_url?>contact/api/engine/manage_contact.php",
autoPrepare: true,
@@ -191,14 +198,34 @@
}
function retreive_for_edit() {
function retrieve_contact_type() {
return ajax_request({
url: "<?php echo $server_url?>contact/api/engine/contact_type.php",
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
var option = ``;
$.each(res.output, function(key, item) {
option += `<option value='${item.id}'>${item.contact_type}</option>`;
})
$(`select#contact_type`).html(option);
}
});
}
function retrieve_for_edit() {
<?php if(empty($_GET['id'])){ ?>
return false;
<?php } ?>
return ajax_request({
url: "<?php echo $server_url?>contact/api/engine/retrieve_contact_type.php",
url: "<?php echo $server_url?>contact/api/engine/retrieve_contact.php",
autoPrepare: true,
checkRequired: 0,
action: 'read',
@@ -208,6 +235,32 @@
$(`#${key}`).val(item);
});
let item = res.output;
// 2. Handle files specifically (No loop needed for the row)
if (item.files) {
let fileArray = item.files.split(',');
fileArray.forEach(function(fileName) {
if (fileName.trim() !== "") {
// Create a mock file for Dropzone
let mockFile = {
name: fileName,
size: 12345,
accepted: true // Prevents Dropzone from rejecting it if it's already on server
};
// Display it in the UI
myDropzone.displayExistingFile(
mockFile,
"<?php echo $server_url?>uploads/contact/" + fileName
);
// Add to Dropzone's internal file array so it knows it exists
myDropzone.files.push(mockFile);
}
});
}
$(`button[type=submit]`).text('Update');
$(`button[type=reset]`).hide();
@@ -221,7 +274,8 @@
$(async function() {
try {
await retreive_for_edit();
await retrieve_contact_type();
await retrieve_for_edit();
} catch (e) {
console.log(e);
}
+1
View File
@@ -136,6 +136,7 @@ function log_out() {
url: "<?php echo $server_url?>login/api/engine/back.php",
autoPrepare: true,
checkRequired: 0,
action: 'read',
onSuccess: function(res) {
window.location.href = "<?php echo $server_url?>index.php";
Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB