Skip to content

Commit b2de269

Browse files
authored
Add files via upload
0 parents  commit b2de269

3 files changed

Lines changed: 346 additions & 0 deletions

File tree

app.js

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
const API_BASE = "http://localhost:5127/api/items";
2+
3+
const itemForm = document.getElementById("itemForm");
4+
const itemsBody = document.getElementById("itemsBody");
5+
const searchBox = document.getElementById("searchBox");
6+
const refreshBtn = document.getElementById("refreshBtn");
7+
const clearBtn = document.getElementById("clearBtn");
8+
9+
let items = [];
10+
let editingId = null;
11+
12+
async function fetchItems() {
13+
const response = await fetch(API_BASE);
14+
items = await response.json();
15+
renderItems();
16+
}
17+
18+
function renderItems() {
19+
const q = searchBox.value.trim().toLowerCase();
20+
21+
const filtered = items.filter(item =>
22+
item.driverName.toLowerCase().includes(q) ||
23+
item.loadNumber.toLowerCase().includes(q) ||
24+
item.brokerName.toLowerCase().includes(q) ||
25+
item.note.toLowerCase().includes(q) ||
26+
q === ""
27+
);
28+
29+
itemsBody.innerHTML = filtered.map(item => `
30+
<tr>
31+
<td>${item.id}</td>
32+
<td>${escapeHtml(item.driverName)}</td>
33+
<td>${escapeHtml(item.loadNumber)}</td>
34+
<td>${escapeHtml(item.brokerName || "-")}</td>
35+
<td>${escapeHtml(item.status)}</td>
36+
<td>${escapeHtml(item.priority || "Medium")}</td>
37+
<td>${escapeHtml(item.assignedTo || "-")}</td>
38+
<td>${item.claimedBy ? escapeHtml(item.claimedBy) : "-"}</td>
39+
<td>${item.needsReply ? "Yes" : "No"}</td>
40+
<td>${formatDate(item.lastUpdateTime)}</td>
41+
<td>${escapeHtml(item.note || "")}</td>
42+
<td>
43+
<div class="row-actions">
44+
<button onclick="editItem(${item.id})">Edit</button>
45+
<button onclick="claimItem(${item.id})">Claim</button>
46+
<button onclick="unclaimItem(${item.id})">Unclaim</button>
47+
<button onclick="deleteItem(${item.id})">Delete</button>
48+
</div>
49+
</td>
50+
</tr>
51+
`).join("");
52+
}
53+
54+
itemForm.addEventListener("submit", async (e) => {
55+
e.preventDefault();
56+
57+
const payload = {
58+
driverName: document.getElementById("driverName").value.trim(),
59+
loadNumber: document.getElementById("loadNumber").value.trim(),
60+
brokerName: document.getElementById("brokerName").value.trim(),
61+
assignedTo: document.getElementById("assignedTo").value.trim(),
62+
source: document.getElementById("source").value,
63+
status: document.getElementById("status").value,
64+
priority: document.getElementById("priority").value,
65+
note: document.getElementById("note").value.trim(),
66+
needsReply: document.getElementById("needsReply").checked
67+
};
68+
69+
const url = editingId ? `${API_BASE}/${editingId}` : API_BASE;
70+
const method = editingId ? "PUT" : "POST";
71+
72+
const response = await fetch(url, {
73+
method,
74+
headers: { "Content-Type": "application/json" },
75+
body: JSON.stringify(payload)
76+
});
77+
78+
if (!response.ok) {
79+
const error = await response.json();
80+
alert(error.message || "Failed");
81+
return;
82+
}
83+
84+
resetForm();
85+
fetchItems();
86+
});
87+
88+
function editItem(id) {
89+
const item = items.find(x => x.id === id);
90+
if (!item) return;
91+
92+
editingId = id;
93+
document.getElementById("driverName").value = item.driverName;
94+
document.getElementById("loadNumber").value = item.loadNumber;
95+
document.getElementById("brokerName").value = item.brokerName || "";
96+
document.getElementById("assignedTo").value = item.assignedTo || "";
97+
document.getElementById("source").value = item.source || "Email";
98+
document.getElementById("status").value = item.status || "Pending";
99+
document.getElementById("priority").value = item.priority || "Medium";
100+
document.getElementById("note").value = item.note || "";
101+
document.getElementById("needsReply").checked = item.needsReply;
102+
}
103+
104+
async function deleteItem(id) {
105+
if (!confirm("Delete item?")) return;
106+
107+
await fetch(`${API_BASE}/${id}`, { method: "DELETE" });
108+
fetchItems();
109+
}
110+
111+
async function claimItem(id) {
112+
const userName = document.getElementById("claimUser").value.trim();
113+
if (!userName) {
114+
alert("Enter your name in claim field");
115+
return;
116+
}
117+
118+
const response = await fetch(`${API_BASE}/${id}/claim`, {
119+
method: "POST",
120+
headers: { "Content-Type": "application/json" },
121+
body: JSON.stringify({ userName })
122+
});
123+
124+
const result = await response.json();
125+
if (!response.ok) {
126+
alert(result.message || "Claim failed");
127+
return;
128+
}
129+
130+
fetchItems();
131+
}
132+
133+
async function unclaimItem(id) {
134+
await fetch(`${API_BASE}/${id}/unclaim`, {
135+
method: "POST"
136+
});
137+
fetchItems();
138+
}
139+
140+
function resetForm() {
141+
editingId = null;
142+
itemForm.reset();
143+
document.getElementById("source").value = "Email";
144+
document.getElementById("status").value = "Pending";
145+
document.getElementById("priority").value = "Medium";
146+
}
147+
148+
function formatDate(value) {
149+
if (!value) return "-";
150+
return new Date(value).toLocaleString();
151+
}
152+
153+
function escapeHtml(str) {
154+
return String(str)
155+
.replaceAll("&", "&amp;")
156+
.replaceAll("<", "&lt;")
157+
.replaceAll(">", "&gt;")
158+
.replaceAll('"', "&quot;")
159+
.replaceAll("'", "&#039;");
160+
}
161+
162+
clearBtn.addEventListener("click", resetForm);
163+
refreshBtn.addEventListener("click", fetchItems);
164+
searchBox.addEventListener("input", renderItems);
165+
166+
fetchItems();
167+
168+
window.editItem = editItem;
169+
window.deleteItem = deleteItem;
170+
window.claimItem = claimItem;
171+
window.unclaimItem = unclaimItem;

index.html

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Dispatch Tracker</title>
7+
<link rel="stylesheet" href="style.css" />
8+
</head>
9+
<body>
10+
<div class="container">
11+
<h1>Dispatch Tracker</h1>
12+
<p>Track loads and avoid double replies.</p>
13+
14+
<div class="card">
15+
<h2>Add / Edit Item</h2>
16+
<form id="itemForm">
17+
<input id="driverName" placeholder="Driver Name" required />
18+
<input id="loadNumber" placeholder="Load Number" required />
19+
<input id="brokerName" placeholder="Broker Name" />
20+
<input id="assignedTo" placeholder="Assigned To" />
21+
22+
<select id="source">
23+
<option>Email</option>
24+
<option>Group</option>
25+
<option>Board</option>
26+
<option>Phone</option>
27+
<option>Other</option>
28+
</select>
29+
30+
<select id="status">
31+
<option>Pending</option>
32+
<option>In Progress</option>
33+
<option>Updated</option>
34+
<option>Waiting Broker</option>
35+
<option>Done</option>
36+
</select>
37+
38+
<select id="priority">
39+
<option>Low</option>
40+
<option selected>Medium</option>
41+
<option>High</option>
42+
<option>Urgent</option>
43+
</select>
44+
45+
<textarea id="note" placeholder="Note"></textarea>
46+
47+
<label class="check-row">
48+
<input type="checkbox" id="needsReply" />
49+
Needs Reply
50+
</label>
51+
52+
<div class="buttons">
53+
<button type="submit">Save</button>
54+
<button type="button" id="clearBtn">Clear</button>
55+
</div>
56+
</form>
57+
</div>
58+
59+
<div class="card">
60+
<div class="topbar">
61+
<input id="claimUser" placeholder="Your Name For Claim" />
62+
<input id="searchBox" placeholder="Search..." />
63+
<button id="refreshBtn">Refresh</button>
64+
</div>
65+
66+
<table>
67+
<thead>
68+
<tr>
69+
<th>ID</th>
70+
<th>Driver</th>
71+
<th>Load</th>
72+
<th>Broker</th>
73+
<th>Status</th>
74+
<th>Priority</th>
75+
<th>Assigned</th>
76+
<th>Claimed</th>
77+
<th>Reply</th>
78+
<th>Last Update</th>
79+
<th>Note</th>
80+
<th>Actions</th>
81+
</tr>
82+
</thead>
83+
<tbody id="itemsBody"></tbody>
84+
</table>
85+
</div>
86+
</div>
87+
88+
<script src="app.js"></script>
89+
</body>
90+
</html>

style.css

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
* {
2+
box-sizing: border-box;
3+
}
4+
5+
body {
6+
margin: 0;
7+
font-family: Arial, sans-serif;
8+
background: #f4f6fb;
9+
color: #222;
10+
}
11+
12+
.container {
13+
max-width: 1400px;
14+
margin: 20px auto;
15+
padding: 0 16px;
16+
}
17+
18+
.card {
19+
background: white;
20+
border-radius: 14px;
21+
padding: 18px;
22+
margin-bottom: 20px;
23+
box-shadow: 0 6px 18px rgba(0,0,0,0.08);
24+
}
25+
26+
form {
27+
display: grid;
28+
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
29+
gap: 12px;
30+
}
31+
32+
input, select, textarea, button {
33+
padding: 10px 12px;
34+
border-radius: 10px;
35+
border: 1px solid #d0d7e2;
36+
font: inherit;
37+
}
38+
39+
textarea {
40+
grid-column: 1 / -1;
41+
min-height: 80px;
42+
resize: vertical;
43+
}
44+
45+
.check-row {
46+
display: flex;
47+
align-items: center;
48+
gap: 8px;
49+
}
50+
51+
.check-row input {
52+
width: auto;
53+
}
54+
55+
.buttons, .topbar, .row-actions {
56+
display: flex;
57+
gap: 10px;
58+
flex-wrap: wrap;
59+
margin-top: 12px;
60+
}
61+
62+
button {
63+
cursor: pointer;
64+
border: none;
65+
background: #2563eb;
66+
color: white;
67+
font-weight: 600;
68+
}
69+
70+
table {
71+
width: 100%;
72+
border-collapse: collapse;
73+
margin-top: 14px;
74+
}
75+
76+
th, td {
77+
border-bottom: 1px solid #e8edf4;
78+
padding: 10px;
79+
text-align: left;
80+
vertical-align: top;
81+
}
82+
83+
th {
84+
background: #f8fafc;
85+
}

0 commit comments

Comments
 (0)