changes
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
Reference in New Issue
Block a user