mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 09:27:07 +00:00
Add 4D comparisons list Vue component
This commit is contained in:
396
lib/www/client/source/src/views/GroupList.vue
Normal file
396
lib/www/client/source/src/views/GroupList.vue
Normal file
@@ -0,0 +1,396 @@
|
|||||||
|
<template>
|
||||||
|
<v-container fluid>
|
||||||
|
|
||||||
|
<v-data-table
|
||||||
|
:headers="headers"
|
||||||
|
:items="displayItems"
|
||||||
|
item-key="group"
|
||||||
|
:options.sync="options"
|
||||||
|
:expanded.sync="expanded"
|
||||||
|
show-expand
|
||||||
|
:loading="loading"
|
||||||
|
>
|
||||||
|
|
||||||
|
<template v-slot:item.group="{item, value}">
|
||||||
|
<v-chip
|
||||||
|
label
|
||||||
|
small
|
||||||
|
:href="`./${value}`"
|
||||||
|
>{{ value }}</v-chip>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:item.shots_total="{item, value}">
|
||||||
|
<div>{{ item.prime + item.other }}</div>
|
||||||
|
<v-progress-linear
|
||||||
|
background-color="secondary"
|
||||||
|
color="primary"
|
||||||
|
:value="item.prime/(item.prime+item.other)*100"
|
||||||
|
></v-progress-linear>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:item.prime="{item, value}">
|
||||||
|
{{ value }}
|
||||||
|
({{ (value / (item.prime + item.other) * 100).toFixed(1) }}%)
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:item.other="{item, value}">
|
||||||
|
{{ value }}
|
||||||
|
({{ (value / (item.prime + item.other) * 100).toFixed(1) }}%)
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:item.prod_duration="{item, value}">
|
||||||
|
<span v-if="value.days > 2" :title="`${value.days} d ${value.hours} h ${value.minutes} m ${(value.seconds + value.milliseconds/1000).toFixed(3)} s`">
|
||||||
|
{{ value.days }} d
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
{{ value.days }} d {{ value.hours }} h {{ value.minutes }} m {{ (value.seconds + value.milliseconds/1000).toFixed(1) }} s
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:item.prod_distance="{item, value}">
|
||||||
|
{{ (value/1000).toFixed(1) }} km
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:item.shooting_rate_mean="{item, value}">
|
||||||
|
{{ (value).toFixed(2) }} s ±{{ (item.shooting_rate_sd).toFixed(3) }} s
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:item.shots_per_point="{item, value}">
|
||||||
|
<div>
|
||||||
|
{{ ((item.prime + item.other)/item.points).toFixed(1) }}
|
||||||
|
({{ ((((item.prime + item.other)/item.points) / item.num_projects)*100).toFixed(2) }}%)
|
||||||
|
</div>
|
||||||
|
<v-progress-linear
|
||||||
|
:value="((((item.prime + item.other)/item.points) / item.num_projects)*100)"
|
||||||
|
></v-progress-linear>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:expanded-item="{ headers, item }">
|
||||||
|
<td :colspan="headers.length">
|
||||||
|
<v-data-table class="ma-1"
|
||||||
|
:headers="projectHeaders"
|
||||||
|
:items="item.projects"
|
||||||
|
dense
|
||||||
|
hide-default-footer
|
||||||
|
>
|
||||||
|
|
||||||
|
<template v-slot:item.pid="{item, value}">
|
||||||
|
<a :href="`/projects/${value}`" title="Go to project">{{ value }}</a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:item.fsp="{item, value}">
|
||||||
|
<span title="First production shot">{{value.tstamp.substr(0, 10)}}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:item.lsp="{item, value}">
|
||||||
|
<span title="Last production shot">{{value.tstamp.substr(0, 10)}}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:item.prod_duration="{item, value}">
|
||||||
|
<span v-if="value.days > 2" :title="`${value.days} d ${value.hours} h ${value.minutes} m ${(value.seconds + value.milliseconds/1000).toFixed(3)} s`">
|
||||||
|
{{ value.days }} d
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
{{ value.days }} d {{ value.hours }} h {{ value.minutes }} m {{ (value.seconds + value.milliseconds/1000).toFixed(1) }} s
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:item.prod_distance="{item, value}">
|
||||||
|
{{ (value/1000).toFixed(1) }} km
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</v-data-table>
|
||||||
|
</td>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</v-data-table>
|
||||||
|
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
td p:last-of-type {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapActions, mapGetters } from 'vuex';
|
||||||
|
import AccessMixin from '@/mixins/access';
|
||||||
|
|
||||||
|
|
||||||
|
// FIXME send to lib/utils or so
|
||||||
|
/*
|
||||||
|
function duration_to_ms(v) {
|
||||||
|
if (v instanceof Object) {
|
||||||
|
return (
|
||||||
|
(v.days || 0) * 86400000 +
|
||||||
|
(v.hours || 0) * 3600000 +
|
||||||
|
(v.minutes || 0) * 60000 +
|
||||||
|
(v.seconds || 0) * 1000 +
|
||||||
|
(v.milliseconds || 0)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
days: 0,
|
||||||
|
hours: 0,
|
||||||
|
minutes: 0,
|
||||||
|
seconds: 0,
|
||||||
|
milliseconds: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ms_to_duration(v) {
|
||||||
|
const days = Math.floor(v / 86400000);
|
||||||
|
v %= 86400000;
|
||||||
|
const hours = Math.floor(v / 3600000);
|
||||||
|
v %= 3600000;
|
||||||
|
const minutes = Math.floor(v / 60000);
|
||||||
|
v %= 60000;
|
||||||
|
const seconds = Math.floor(v / 1000);
|
||||||
|
const milliseconds = v % 1000;
|
||||||
|
return { days, hours, minutes, seconds, milliseconds };
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalise_duration (v) {
|
||||||
|
return ms_to_duration(duration_to_ms(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
function add_durations(a, b) {
|
||||||
|
return ms_to_duration(duration_to_ms(a) + duration_to_ms(b));
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "GroupList",
|
||||||
|
|
||||||
|
components: {
|
||||||
|
},
|
||||||
|
|
||||||
|
mixins: [
|
||||||
|
AccessMixin
|
||||||
|
],
|
||||||
|
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
value: "group",
|
||||||
|
text: "Group name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "num_projects",
|
||||||
|
text: "Number of campaigns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "lines",
|
||||||
|
text: "Preplot lines"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "points",
|
||||||
|
text: "Preplot points"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "sequences",
|
||||||
|
text: "Total sequences"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "shots_total",
|
||||||
|
text: "Total shots"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "prime",
|
||||||
|
text: "Total prime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "other",
|
||||||
|
text: "Total reshoot + infill"
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
value: "ntba",
|
||||||
|
text: "Total NTBA"
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
value: "prod_duration",
|
||||||
|
text: "Total duration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "prod_distance",
|
||||||
|
text: "Total distance"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "shooting_rate_mean",
|
||||||
|
text: "Shooting rate (mean)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "shots_per_point",
|
||||||
|
text: "Shots per point"
|
||||||
|
},
|
||||||
|
],
|
||||||
|
items: [],
|
||||||
|
expanded: [],
|
||||||
|
options: { sortBy: ["group"], sortDesc: [false] },
|
||||||
|
|
||||||
|
projectHeaders: [
|
||||||
|
{
|
||||||
|
value: "pid",
|
||||||
|
text: "ID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "name",
|
||||||
|
text: "Name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "fsp",
|
||||||
|
text: "Start"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "lsp",
|
||||||
|
text: "Finish"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "lines",
|
||||||
|
text: "Preplot lines"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "seq_final",
|
||||||
|
text: "Num. of sequences"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "prod_duration",
|
||||||
|
text: "Duration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "prod_distance",
|
||||||
|
text: "Distance"
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
// Context menu stuff
|
||||||
|
contextMenuShow: false,
|
||||||
|
contextMenuX: 0,
|
||||||
|
contextMenuY: 0,
|
||||||
|
contextMenuItem: null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
// FIXME Eventually need to move this into Vuex
|
||||||
|
groups: []
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
displayItems () {
|
||||||
|
return this.items.filter(i => i.prod_distance);
|
||||||
|
},
|
||||||
|
|
||||||
|
...mapGetters(['loading', 'groups'])
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
/*
|
||||||
|
async prepareGroups () {
|
||||||
|
//const groups = await this.api(["/prospects"]);
|
||||||
|
//console.log("groups", groups);
|
||||||
|
const groups = {};
|
||||||
|
|
||||||
|
for (const project of this.projects) {
|
||||||
|
|
||||||
|
if (!project.prod_distance) {
|
||||||
|
// This project has no production data (either not started yet
|
||||||
|
// or production data has not been imported) so we skip it.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!project.prod_duration.days) {
|
||||||
|
project.prod_duration = normalise_duration(project.prod_duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const name of project.groups) {
|
||||||
|
if (!(name in groups)) {
|
||||||
|
groups[name] = {
|
||||||
|
group: name,
|
||||||
|
num_projects: 0,
|
||||||
|
lines: 0,
|
||||||
|
points: 0,
|
||||||
|
sequences: 0,
|
||||||
|
// Shots:
|
||||||
|
prime: 0,
|
||||||
|
other: 0,
|
||||||
|
ntba: 0,
|
||||||
|
prod_duration: {
|
||||||
|
days: 0,
|
||||||
|
hours: 0,
|
||||||
|
minutes: 0,
|
||||||
|
seconds: 0,
|
||||||
|
milliseconds: 0
|
||||||
|
},
|
||||||
|
prod_distance: 0,
|
||||||
|
shooting_rate: [],
|
||||||
|
projects: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const group = groups[name];
|
||||||
|
|
||||||
|
group.num_projects++;
|
||||||
|
group.lines = Math.max(group.lines, project.lines); // In case preplots changed
|
||||||
|
group.points = Math.max(group.points, project.total); // Idem
|
||||||
|
group.sequences += project.seq_final;
|
||||||
|
group.prime += project.prime;
|
||||||
|
group.other += project.other;
|
||||||
|
//group.ntba += project.ntba;
|
||||||
|
group.prod_duration = add_durations(group.prod_duration, project.prod_duration);
|
||||||
|
group.prod_distance += project.prod_distance;
|
||||||
|
group.shooting_rate.push(project.shooting_rate);
|
||||||
|
group.projects.push(project);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.groups = [];
|
||||||
|
for (const group of Object.values(groups)) {
|
||||||
|
group.shooting_rate_mean = d3a.mean(group.shooting_rate);
|
||||||
|
group.shooting_rate_sd = d3a.deviation(group.shooting_rate);
|
||||||
|
delete group.shooting_rate;
|
||||||
|
|
||||||
|
this.groups.push(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
|
||||||
|
async list () {
|
||||||
|
this.items = [...this.groups];
|
||||||
|
},
|
||||||
|
|
||||||
|
async load () {
|
||||||
|
await this.refreshProjects();
|
||||||
|
//await this.prepareGroups();
|
||||||
|
await this.list();
|
||||||
|
},
|
||||||
|
|
||||||
|
registerNotificationHandlers () {
|
||||||
|
this.$store.dispatch('registerHandler', {
|
||||||
|
table: 'project`',
|
||||||
|
|
||||||
|
handler: (context, message) => {
|
||||||
|
if (message.payload?.table == "public") {
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
...mapActions(["api", "showSnack", "refreshProjects"])
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted () {
|
||||||
|
this.registerNotificationHandlers();
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user