This commit is contained in:
gsinghpal
2026-03-09 15:21:22 -04:00
parent a3e85a23ef
commit acd3fc455e
243 changed files with 20459 additions and 4197 deletions

View File

@@ -138,6 +138,75 @@ $transition-speed: .25s;
font-weight: 500;
}
// ── Technician filter chips ─────────────────────────────────────────
.fc_tech_filters {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.fc_tech_chip {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 3px 10px 3px 4px;
font-size: 11px;
font-weight: 600;
border: 1px solid $border-color;
border-radius: 14px;
background: transparent;
color: $text-muted;
cursor: pointer;
transition: all .15s;
line-height: 18px;
max-width: 100%;
overflow: hidden;
&:hover {
border-color: rgba($primary, .35);
color: $body-color;
background: rgba($primary, .06);
}
&--active {
background: $primary !important;
color: #fff !important;
border-color: $primary !important;
.fc_tech_chip_avatar {
background: rgba(#fff, .25);
color: #fff;
}
}
&--all {
padding: 3px 10px;
color: $body-color;
font-weight: 500;
&:hover { background: rgba($primary, .1); }
}
}
.fc_tech_chip_avatar {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
border-radius: 50%;
background: rgba($secondary, .15);
color: $body-color;
font-size: 9px;
font-weight: 700;
flex-shrink: 0;
}
.fc_tech_chip_name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
// Collapsed toggle button (floating)
.fc_sidebar_toggle_btn {
position: absolute;

View File

@@ -180,9 +180,22 @@ const SOURCE_COLORS = {
mobility: "#198754",
};
/** Extract unique technicians from task data, sorted by name */
function extractTechnicians(tasksData) {
const map = {};
for (const t of tasksData) {
if (t.technician_id) {
const [id, name] = t.technician_id;
if (!map[id]) {
map[id] = { id, name, initials: initialsOf(name) };
}
}
}
return Object.values(map).sort((a, b) => a.name.localeCompare(b.name));
}
/** Group + sort tasks, returning { groupKey: { label, tasks[], count } } */
function groupTasks(tasksData, localInstanceId) {
// Sort by date ASC, time ASC
function groupTasks(tasksData, localInstanceId, visibleTechIds) {
const sorted = [...tasksData].sort((a, b) => {
const da = a.scheduled_date || "";
const db = b.scheduled_date || "";
@@ -190,6 +203,8 @@ function groupTasks(tasksData, localInstanceId) {
return (a.time_start || 0) - (b.time_start || 0);
});
const hasTechFilter = visibleTechIds && Object.keys(visibleTechIds).length > 0;
const groups = {};
const order = [GROUP_PENDING, GROUP_YESTERDAY, GROUP_TODAY, GROUP_TOMORROW, GROUP_THIS_WEEK, GROUP_LATER];
for (const key of order) {
@@ -205,12 +220,15 @@ function groupTasks(tasksData, localInstanceId) {
const dayCounters = {};
for (const task of sorted) {
const techId = task.technician_id ? task.technician_id[0] : 0;
if (hasTechFilter && !visibleTechIds[techId]) continue;
const g = classifyTask(task);
const dayKey = task.scheduled_date || "none";
dayCounters[dayKey] = (dayCounters[dayKey] || 0) + 1;
task._scheduleNum = dayCounters[dayKey];
task._group = g;
task._dayColor = DAY_COLORS[g] || "#6b7280"; // Pin colour by day
task._dayColor = DAY_COLORS[g] || "#6b7280";
task._statusColor = STATUS_COLORS[task.status] || "#6b7280";
task._statusLabel = STATUS_LABELS[task.status] || task.status || "";
task._statusIcon = STATUS_ICONS[task.status] || "fa-circle";
@@ -228,7 +246,6 @@ function groupTasks(tasksData, localInstanceId) {
groups[g].count++;
}
// Return only non-empty groups in order
return order.map((k) => groups[k]).filter((g) => g.count > 0);
}
@@ -259,12 +276,10 @@ export class FusionTaskMapController extends Component {
showRoute: true,
taskCount: 0,
techCount: 0,
// Sidebar
sidebarOpen: true,
groups: [], // [{key, label, tasks[], count}]
collapsedGroups: {}, // {groupKey: true}
activeTaskId: null, // Highlighted task
// Day filters for map pins (which groups show on map)
groups: [],
collapsedGroups: {},
activeTaskId: null,
visibleGroups: {
[GROUP_YESTERDAY]: false,
[GROUP_TODAY]: true,
@@ -272,6 +287,8 @@ export class FusionTaskMapController extends Component {
[GROUP_THIS_WEEK]: false,
[GROUP_LATER]: false,
},
allTechnicians: [],
visibleTechIds: {},
});
// Yesterday collapsed by default in sidebar list
@@ -339,9 +356,17 @@ export class FusionTaskMapController extends Component {
this.tasksData = result.tasks || [];
this.locationsData = result.locations || [];
this.techStartLocations = result.tech_start_locations || {};
this.state.taskCount = this.tasksData.length;
this.state.allTechnicians = extractTechnicians(this.tasksData);
this._rebuildGroups();
}
_rebuildGroups() {
this.state.groups = groupTasks(
this.tasksData, this.localInstanceId, this.state.visibleTechIds,
);
const filteredCount = this.state.groups.reduce((s, g) => s + g.count, 0);
this.state.taskCount = filteredCount;
this.state.techCount = this.locationsData.length;
this.state.groups = groupTasks(this.tasksData, this.localInstanceId);
}
async _loadAndRender() {
@@ -1008,6 +1033,28 @@ export class FusionTaskMapController extends Component {
this._renderMarkers();
}
// ── Technician filter ─────────────────────────────────────────────
toggleTechFilter(techId) {
if (this.state.visibleTechIds[techId]) {
delete this.state.visibleTechIds[techId];
} else {
this.state.visibleTechIds[techId] = true;
}
this._rebuildGroups();
this._renderMarkers();
}
isTechVisible(techId) {
const hasFilter = Object.keys(this.state.visibleTechIds).length > 0;
return !hasFilter || !!this.state.visibleTechIds[techId];
}
showAllTechs() {
this.state.visibleTechIds = {};
this._rebuildGroups();
this._renderMarkers();
}
// ── Top bar actions ─────────────────────────────────────────────
toggleTraffic() {
this.state.showTraffic = !this.state.showTraffic;

View File

@@ -52,6 +52,22 @@
<button class="fc_day_chip fc_day_chip--all" t-on-click="showAllDays"
title="Show all">All</button>
</div>
<!-- Technician filter -->
<t t-if="state.allTechnicians.length > 1">
<div class="fc_tech_filters mt-2">
<t t-foreach="state.allTechnicians" t-as="tech" t-key="tech.id">
<button t-att-class="'fc_tech_chip' + (isTechVisible(tech.id) ? ' fc_tech_chip--active' : '')"
t-on-click="() => this.toggleTechFilter(tech.id)"
t-att-title="tech.name">
<span class="fc_tech_chip_avatar" t-esc="tech.initials"/>
<span class="fc_tech_chip_name" t-esc="tech.name"/>
</button>
</t>
<button class="fc_tech_chip fc_tech_chip--all" t-on-click="showAllTechs"
title="Show all technicians">All</button>
</div>
</t>
</div>
<!-- Sidebar body: grouped task list -->