complete overhaul
This commit is contained in:
1 parent
0bc92cdb8b
commit
ee6c7719cf
18 files changed
+751
-440
No files matched your search
@@ -0,0 +1,38 @@
|
||||
<!-- index.html (served by PHP) -->
|
||||
<input type="file" id="photo" style="display:none;">
|
||||
<div id="drop" style="border:2px dashed #bbb; padding:20px;">
|
||||
Drag photo here or click to choose
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const drop = document.getElementById('drop');
|
||||
const photoInput = document.getElementById('photo');
|
||||
|
||||
drop.onclick = () => photoInput.click();
|
||||
|
||||
drop.addEventListener('dragover', e => { e.preventDefault(); drop.style.background='#eef'; });
|
||||
drop.addEventListener('dragleave', e => { e.preventDefault(); drop.style.background=''; });
|
||||
|
||||
drop.addEventListener('drop', e => {
|
||||
e.preventDefault();
|
||||
drop.style.background='';
|
||||
const file = e.dataTransfer.files[0];
|
||||
upload(file);
|
||||
});
|
||||
|
||||
photoInput.addEventListener('change', () => upload(photoInput.files[0]));
|
||||
|
||||
async function upload(file) {
|
||||
// 1️⃣ Send file to PHP, which will forward it to Flask
|
||||
const form = new FormData();
|
||||
form.append('photo', file);
|
||||
|
||||
const res = await fetch('/api/upload_photo', { // <-- PHP endpoint
|
||||
method: 'POST',
|
||||
body: form
|
||||
});
|
||||
|
||||
const json = await res.json();
|
||||
console.log('Uploaded:', json);
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
// providers.php
|
||||
// Simple page that pulls provider data via the Flask API.
|
||||
// It expects the Flask API to be reachable at http://127.0.0.1:5000
|
||||
// The API endpoint for providers is /providers.
|
||||
|
||||
// --- Helper to call the API -------------------------------------------
|
||||
function callApi(string $method, string $path, $body = null)
|
||||
{
|
||||
$url = 'http://172.25.1.105:5000' . $path;
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
||||
|
||||
if ($body !== null) {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
|
||||
}
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode >= 400) {
|
||||
return null; // or throw an exception
|
||||
}
|
||||
|
||||
return json_decode($response, true);
|
||||
}
|
||||
|
||||
// --- Fetch all providers -----------------------------------------------
|
||||
$providers = callApi('GET', '/providers');
|
||||
$providers = $providers ?? [];
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Providers</title>
|
||||
|
||||
<link rel="stylesheet" href="https://cosmostat.matt-cloud.com/src/styles.css">
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 2rem; }
|
||||
#search { margin-bottom: 1rem; padding: 0.5rem; width: 300px; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
img { max-width: 60px; height: auto; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>Providers</h1>
|
||||
<input type="text" id="search" placeholder="Search providers...">
|
||||
<table id="providers-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-field="id">ID</th>
|
||||
<th data-field="name">Name</th>
|
||||
<th data-field="phone">Phone</th>
|
||||
<th data-field="age">Age</th>
|
||||
<th data-field="photo">Photo</th>
|
||||
<th data-field="rates">Rates</th>
|
||||
<th data-field="private">Private</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<script>
|
||||
const providers = <?php echo json_encode($providers); ?>;
|
||||
let currentSort = { field: null, asc: true };
|
||||
|
||||
function renderTable(data) {
|
||||
const tbody = document.querySelector('#providers-table tbody');
|
||||
tbody.innerHTML = '';
|
||||
data.forEach(p => {
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = `
|
||||
<td>${p.id}</td>
|
||||
<td>${p.name}</td>
|
||||
<td>${p.phone || ''}</td>
|
||||
<td>${p.age || ''}</td>
|
||||
<td>${p.photo ? `<img src="${p.photo}" alt="Photo">` : ''}</td>
|
||||
<td>${p.rates}</td>
|
||||
<td>${p.private ? 'Yes' : 'No'}</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
function sortData(field) {
|
||||
if (currentSort.field === field) {
|
||||
currentSort.asc = !currentSort.asc;
|
||||
} else {
|
||||
currentSort.field = field;
|
||||
currentSort.asc = true;
|
||||
}
|
||||
const sorted = providers.slice().sort((a, b) => {
|
||||
let aVal = a[field];
|
||||
let bVal = b[field];
|
||||
if (['id', 'age', 'private'].includes(field)) {
|
||||
aVal = Number(aVal);
|
||||
bVal = Number(bVal);
|
||||
} else {
|
||||
aVal = (aVal || '').toString().toLowerCase();
|
||||
bVal = (bVal || '').toString().toLowerCase();
|
||||
}
|
||||
if (aVal < bVal) return currentSort.asc ? -1 : 1;
|
||||
if (aVal > bVal) return currentSort.asc ? 1 : -1;
|
||||
return 0;
|
||||
});
|
||||
renderTable(sorted);
|
||||
}
|
||||
|
||||
renderTable(providers);
|
||||
|
||||
document.querySelectorAll('#providers-table th').forEach(th => {
|
||||
th.addEventListener('click', () => {
|
||||
const field = th.getAttribute('data-field');
|
||||
sortData(field);
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('search').addEventListener('input', e => {
|
||||
const query = e.target.value.toLowerCase();
|
||||
const filtered = providers.filter(p => {
|
||||
return Object.values(p).some(v => {
|
||||
if (!v) return false;
|
||||
return String(v).toLowerCase().includes(query);
|
||||
});
|
||||
});
|
||||
renderTable(filtered);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Fitness Tracker</title>
|
||||
<style>
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px; }
|
||||
th { background:#f0f0f0; }
|
||||
#drop-zone { border: 2px dashed #aaa; padding: 1rem; text-align:center; cursor:pointer; }
|
||||
#drop-zone.hover { background:#e0e0ff; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Providers</h1>
|
||||
<button id="add-provider-btn">Add Provider</button>
|
||||
<table id="providers-table">
|
||||
<thead><tr><th>ID</th><th>Name</th><th>Phone</th><th>Photo</th></tr></thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
|
||||
<!-- Add Provider Modal -->
|
||||
<div id="modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.5);">
|
||||
<div style="background:#fff; margin:5% auto; padding:1rem; width:300px; position:relative;">
|
||||
<h2>Add Provider</h2>
|
||||
<label>Name: <input type="text" id="p-name"></label><br>
|
||||
<label>Age: <input type="number" id="p-age"></label><br>
|
||||
<label>Phone: <input type="text" id="p-phone"></label><br>
|
||||
<label>Rates: <input type="text" id="p-rates" placeholder='{"hourly":70}'></label><br>
|
||||
<label>Private: <input type="checkbox" id="p-private"></label><br>
|
||||
|
||||
<div id="drop-zone">Drag photo or click to upload</div>
|
||||
<input type="file" id="p-file" style="display:none;">
|
||||
<br>
|
||||
<button id="save-provider">Save</button>
|
||||
<button id="cancel-provider">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const API = 'http://localhost:5000';
|
||||
|
||||
// ----------- Fetch & render providers -----------------
|
||||
async function loadProviders() {
|
||||
const res = await fetch(`${API}/providers`);
|
||||
const data = await res.json();
|
||||
const tbody = document.querySelector('#providers-table tbody');
|
||||
tbody.innerHTML = '';
|
||||
data.forEach(p => {
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = `
|
||||
<td>${p.id}</td>
|
||||
<td>${p.name}</td>
|
||||
<td>${p.phone}</td>
|
||||
<td><img src="${p.photo}" width="50"></td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
loadProviders();
|
||||
|
||||
// ----------- Modal handling -----------------
|
||||
const modal = document.getElementById('modal');
|
||||
const addBtn = document.getElementById('add-provider-btn');
|
||||
const cancelBtn = document.getElementById('cancel-provider');
|
||||
const dropZone = document.getElementById('drop-zone');
|
||||
const fileInput = document.getElementById('p-file');
|
||||
let uploadedPhotoUrl = null;
|
||||
|
||||
addBtn.onclick = () => { modal.style.display='block'; }
|
||||
cancelBtn.onclick = () => { modal.style.display='none'; }
|
||||
|
||||
dropZone.onclick = () => fileInput.click();
|
||||
|
||||
fileInput.onchange = () => uploadFile(fileInput.files[0]);
|
||||
|
||||
dropZone.addEventListener('dragover', e => { e.preventDefault(); dropZone.classList.add('hover'); });
|
||||
dropZone.addEventListener('dragleave', e => { e.preventDefault(); dropZone.classList.remove('hover'); });
|
||||
dropZone.addEventListener('drop', e => {
|
||||
e.preventDefault(); dropZone.classList.remove('hover');
|
||||
uploadFile(e.dataTransfer.files[0]);
|
||||
});
|
||||
|
||||
async function uploadFile(file) {
|
||||
const formData = new FormData();
|
||||
formData.append('photo', file);
|
||||
const resp = await fetch(`${API}/upload_photo`, { method: 'POST', body: formData });
|
||||
uploadedPhotoUrl = await resp.text(); // /uploads/xxxx.jpg
|
||||
dropZone.textContent = '✓ Uploaded';
|
||||
}
|
||||
|
||||
// ----------- Save provider -----------------
|
||||
const saveBtn = document.getElementById('save-provider');
|
||||
saveBtn.onclick = async () => {
|
||||
const payload = {
|
||||
name: document.getElementById('p-name').value,
|
||||
age: parseInt(document.getElementById('p-age').value, 10),
|
||||
phone: document.getElementById('p-phone').value,
|
||||
rates: JSON.parse(document.getElementById('p-rates').value),
|
||||
private: document.getElementById('p-private').checked,
|
||||
photo: uploadedPhotoUrl
|
||||
};
|
||||
await fetch(`${API}/providers`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
modal.style.display='none';
|
||||
loadProviders(); // refresh table
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user