Added new Dashboard

This commit is contained in:
Luca Haid
2025-01-09 16:37:36 +01:00
parent 125316234d
commit 6aa797a006
6 changed files with 620 additions and 5 deletions

View File

@@ -0,0 +1,230 @@
<?php
//TODO: interessen
class DashboardNewController extends mfBaseController {
private User $me;
protected function init(): void {
$me = new User();
$me->loadMe();
$this->layout()->set("me", $me);
$this->me = $me;
}
protected function indexAction() {
// $this->layout()->set('additionalJS', ["plugins/chart.js/chart.4.4.6.js", "plugins/chart.js/chartjs-adapter-moment.min.js"]);
Helper::renderVue($this, "DashboardNew", $this->mod, []);
}
protected function getNetOwnerFilterOptionsAction() {
if (!$this->me->is("Admin")) return; // TODO: enable for RML and Energie Steiermark
$allPreorderCampaigns = PreordercampaignModel::getAll();
$netowners = [];
foreach ($allPreorderCampaigns as $campaign) {
$network = new Network($campaign->network_id);
$networkOwner = new Address($network->owner_id);
$ownerName = $networkOwner->getCompanyOrName();
if (!in_array($network->owner_id, array_column($netowners, 'value'))) {
$netowners[] = ['text' => $ownerName, 'value' => $network->owner_id];
}
}
self::returnJson($netowners);
}
protected function getCampaignFilterOptionsAction() {
if (!$this->me->is("Admin")) return; // TODO: enable for RML and Energie Steiermark
$post = json_decode(file_get_contents('php://input'), true);
$netowner_ids = isset($post['netOwners']) ? [$post['netOwners']] : [];
$campaigns = [];
$all_campaigns = PreordercampaignModel::getAll();
if (!empty($netowner_ids)) {
foreach ($all_campaigns as $campaign) {
$networkOwner = $campaign->network->owner_id;
if (!in_array($networkOwner, $netowner_ids)) continue;
if (!in_array($campaign->id, array_column($campaigns, 'value'))) {
$campaigns[] = ['text' => $campaign->name, 'value' => $campaign->id];
}
}
} else {
foreach ($all_campaigns as $campaign) {
if (!in_array($campaign->id, array_column($campaigns, 'value'))) {
$campaigns[] = ['text' => $campaign->name, 'value' => $campaign->id];
}
}
}
self::returnJson($campaigns);
}
protected function getCampaignGemeindeFilterOptionsAction() {
if (!$this->me->is("Admin")) return; // TODO: enable for RML and Energie Steiermark
$post = json_decode(file_get_contents('php://input'), true);
$netowner_ids = isset($post['netOwners']) ? [$post['netOwners']] : [];
$campaign_ids = isset($post['campaigns']) ? [$post['campaigns']] : [];
$campaigns = [];
$all_campaigns = PreordercampaignModel::getAll();
if (!empty($netowner_ids)) {
foreach ($all_campaigns as $campaign) {
$networkOwner = $campaign->network->owner_id;
if (!in_array($networkOwner, $netowner_ids)) continue;
if (in_array($campaign->id, $campaign_ids)) continue;
$campaigns[] = $campaign->id;
}
} else {
foreach ($all_campaigns as $campaign) {
if (in_array($campaign->id, $campaign_ids)) continue;
$campaigns[] = $campaign->id;
}
}
if (!empty($campaign_ids)) {
self::returnJson($this->getGemeindenFromCampaigns($campaign_ids));
return;
}
self::returnJson($this->getGemeindenFromCampaigns($campaigns));
}
private function getGemeindenFromCampaigns($campaignids = []) {
$gemeinden = [];
foreach ($campaignids as $campaign_id) {
$campaign = new Preordercampaign($campaign_id);
if (!$campaign || !$campaign->network_id || !$campaign->network->adb_netzgebiet_id) continue;
$gems = $campaign->network->adb_netzgebiet->gemeinden;
if (!is_array($gems)) continue;
foreach ($gems as $gem) {
if (!in_array($gem->id, array_column($gemeinden, 'value'))) $gemeinden[] = ['text' => $gem->name, 'value' => $gem->id];
}
}
ksort($gemeinden);
return $gemeinden;
}
protected function getDashboardDataAction() {
$post = json_decode(file_get_contents('php://input'), true);
$netowner_ids = $post['netOwners'] === '' ? [] : [$post['netOwners']] ?? [];
$campaign_ids = $post['campaigns'] === '' ? [] : [$post['campaigns']] ?? [];
$gemeinde_ids = $post['gemeinden'] === '' ? [] : [$post['gemeinden']] ?? [];
if (!empty($netowner_ids)) {
$campaign_ids = empty($campaign_ids) ?
array_map(fn($campaign) => $campaign->id, PreordercampaignModel::getAll()) :
$campaign_ids;
$campaign_ids = array_filter($campaign_ids, function ($campaign_id) use ($netowner_ids) {
$campaign = new Preordercampaign($campaign_id);
return in_array($campaign->network->owner_id, $netowner_ids);
});
}
$order_max_homes = $this->getTotalHomes($campaign_ids, $gemeinde_ids);
$efh_connection_types = ["single-dwelling", "business"];
$mph_connection_types = ["apartment-building", "apartment", "multi-dwelling"];
$countFunction = function($params, $statusFlag = null) use ($campaign_ids, $gemeinde_ids) {
$baseParams = ["preordercampaign_id" => $campaign_ids];
if (!empty($gemeinde_ids)) {
$baseParams["gemeinde_id"] = $gemeinde_ids;
}
$params = array_merge($baseParams, $params);
return $statusFlag ?
PreorderModel::countStatusFlagsActive($params, $statusFlag) :
PreorderModel::countActive($params);
};
$counts = [
'count_orders' => $countFunction([]),
'total_efh_orders' => $countFunction(["connection_type" => $efh_connection_types]),
'total_efh_orders_vorsorge' => $countFunction(["connection_type" => $efh_connection_types, "type" => "provision"]),
'total_efh_orders_vollanschluss' => $countFunction(["connection_type" => $efh_connection_types, "type" => "order"]),
'total_mph_orders' => $countFunction(["connection_type" => $mph_connection_types]),
'total_mph_orders_vorsorge' => $countFunction(["connection_type" => $mph_connection_types, "type" => "provision"]),
'total_mph_orders_vollanschluss' => $countFunction(["connection_type" => $mph_connection_types, "type" => "order"]),
'baufortschritt_140' => $countFunction([">status_code" => "139"]),
'installationspaket_erhalten' => $countFunction(["connection_type" => $efh_connection_types], 145),
'lehrrohr_im_haus' => $countFunction(["connection_type" => $efh_connection_types], 200),
'inhouse_kabel_verlegt_efh' => $countFunction(["connection_type" => $efh_connection_types], 242),
'inhouse_kabel_verlegt_mph' => $countFunction(["connection_type" => $mph_connection_types], 242),
'installationsfortschritt_245' => $countFunction([">status_code" => "244"]),
'ont_installiert_300' => $countFunction([">status_code" => "299"]),
'vollanschluss_dokumentiert_350' => $countFunction(["status_code" => ["350","500"], "type" => "order"]),
'vorsorge_dokumentiert_351' => $countFunction(["status_code" => ["351","500"], "type" => "provision"]),
'provider_bestellt_500' => $countFunction(["status_code" => "500"])
];
self::returnJson([
'order_max_home_addrdb' => $order_max_homes,
'order_actual_order' => $counts['count_orders'],
'order_efh' => $counts['total_efh_orders'],
'order_efh_vorsorge' => $counts['total_efh_orders_vorsorge'],
'order_efh_vollanschluss' => $counts['total_efh_orders_vollanschluss'],
'order_mph' => $counts['total_mph_orders'],
'order_mph_vorsorge' => $counts['total_mph_orders_vorsorge'],
'order_mph_vollanschluss' => $counts['total_mph_orders_vollanschluss'],
'baufortschritt_140' => $counts['baufortschritt_140'],
'installationspaket_erhalten' => $counts['installationspaket_erhalten'],
'lehrrohr_im_haus' => $counts['lehrrohr_im_haus'],
'inhouse_kabel_verlegt_efh' => $counts['inhouse_kabel_verlegt_efh'],
'inhouse_kabel_verlegt_mph' => $counts['inhouse_kabel_verlegt_mph'],
'installationsfortschritt_245' => $counts['installationsfortschritt_245'],
'ont_installiert_300' => $counts['ont_installiert_300'],
'vollanschluss_dokumentiert_350' => $counts['vollanschluss_dokumentiert_350'],
'vorsorge_dokumentiert_351' => $counts['vorsorge_dokumentiert_351'],
'provider_bestellt_500' => $counts['provider_bestellt_500']
]);
}
private function getTotalHomes(array $preordercampaign_id = [], array $gemeinde_id = []) {
$baseSQL = "SELECT COUNT(adb_wohneinheit.id) as cnt FROM `" . ADDRESSDB_DBNAME . "`.Wohneinheit adb_wohneinheit
LEFT JOIN `" . ADDRESSDB_DBNAME . "`.Hausnummer adb_hausnummer ON (adb_wohneinheit.hausnummer_id = adb_hausnummer.id)
LEFT JOIN `" . ADDRESSDB_DBNAME . "`.Strasse adb_strasse ON (adb_hausnummer.strasse_id = adb_strasse.id)
WHERE 1=1";
$where = "";
if (!empty($preordercampaign_id)) {
$netzgebiet_ids = [];
foreach ($preordercampaign_id as $campaign_id) {
$campaign = new Preordercampaign($campaign_id);
if ($campaign->network_id) {
$network = new Network($campaign->network_id);
$netzgebiet_ids[] = $network->adb_netzgebiet_id;
}
}
$where .= " AND adb_hausnummer.netzgebiet_id IN (" . implode(',', array_map('intval', $netzgebiet_ids)) . ")";
}
if (!empty($gemeinde_id)) {
$where .= " AND adb_strasse.gemeinde_id IN (" . implode(',', array_map('intval', $gemeinde_id)) . ")";
}
$sql = $baseSQL . $where;
$res = $this->db()->query($sql);
if ($this->db()->num_rows($res)) {
$data = $this->db()->fetch_object($res);
return $data->cnt;
}
return 0;
}
}

View File

@@ -365,7 +365,6 @@ class PreorderModel {
&& !array_key_exists("<status_id", $filter) && !array_key_exists(">status_id", $filter) && !array_key_exists("status_id", $filter) ) {
$filter["<status_code"] = 899;
}
return self::count($filter);
}
@@ -395,7 +394,7 @@ class PreorderModel {
LEFT OUTER JOIN (SELECT adb_wohneinheit_id, count(*) cnt, MAX(rimo_status) as rimo_status, MAX(rimo_name) as rimo_name, MAX(rimo_team_name) as rimo_team_name, MAX(rimo_team_id) as rimo_team_id FROM `".FRONKDB_DBNAME."`.RimoWorkorder GROUP BY adb_wohneinheit_id) workorder ON adb_wohneinheit.id = workorder.adb_wohneinheit_id
WHERE $where
";
mfLoghandler::singleton()->debug($sql);
$res = $db->query($sql);
@@ -833,5 +832,42 @@ class PreorderModel {
//var_dump($filter, $where);exit;
return $where;
}
public static function countStatusFlagsActive($filter = [], $statusFlag = null) {
if ($statusFlag === null) {
die("Please select a statusflag");
}
if(!is_array($filter)) return false;
if(!array_key_exists("deleted", $filter)) {
$filter["deleted"] = null;
}
if( !array_key_exists("<status_code", $filter) && !array_key_exists(">status_code", $filter) && !array_key_exists("status_code", $filter)
&& !array_key_exists("<status_id", $filter) && !array_key_exists(">status_id", $filter) && !array_key_exists("status_id", $filter) ) {
$filter["<status_code"] = 899;
}
$db = FronkDB::singleton();
$where = self::getSqlFilter($filter);
$sql = "SELECT COUNT(*) as cnt
FROM `".FRONKDB_DBNAME."`.Preorder tt_preorder
LEFT JOIN `".FRONKDB_DBNAME."`.Preorderstatus tt_preorderstatus ON tt_preorder.status_id = tt_preorderstatus.id
INNER JOIN `".FRONKDB_DBNAME."`.PreorderStatusflagValue psfv ON tt_preorder.id = psfv.preorder_id
INNER JOIN `".FRONKDB_DBNAME."`.PreorderStatusflag psf ON psfv.flag_id = psf.id
WHERE $where AND psf.code = ".$statusFlag." AND psfv.value = 1";
mfLoghandler::singleton()->debug($sql);
$res = $db->query($sql);
if ($db->num_rows($res)) {
$data = $db->fetch_object($res);
return $data->cnt;
}
return 0;
}
}

View File

@@ -126,8 +126,10 @@ class Preordercampaign extends mfBaseModel {
$total_md = 0;
$netzgebiet_ids = null;
foreach($this->getProperty("salesclusters") as $scluster) {
if($scluster->id) $netzgebiet_ids[] = $scluster->id;
if ($this->getProperty("salesclusters")) {
foreach($this->getProperty("salesclusters") as $scluster) {
if($scluster->id) $netzgebiet_ids[] = $scluster->id;
}
}
if(is_array($netzgebiet_ids) && count($netzgebiet_ids)) {
foreach(ADBNetzgebietModel::search(["netzgebiet_id" => $netzgebiet_ids]) as $netzgebiet) {

View File

@@ -0,0 +1,19 @@
<?php /** @noinspection ALL */
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class AddVoiceCallHistoryStartIndex extends AbstractMigration {
public function up(): void {
if ($this->getEnvironment() == "thetool") {
//VoiceCallHistory Table add start index
$this->table("PreorderStatusflagValue")->addIndex(["flag_id"])->save();
}
}
public function down(): void {
if ($this->getEnvironment() == "thetool") {
$this->table("PreorderStatusflagValue")->removeIndex(["flag_id"])->save();
}
}
}

View File

@@ -0,0 +1,55 @@
.dashboard-container {
width: 100%;
max-width: 600px;
margin: 0 auto;
}
.dashboard-content {
display: flex;
flex-direction: column;
gap: 1rem;
}
.filter-section,
.campaign-section {
width: 100%;
}
.dashboard-cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
.dashboard-data-selector {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
@media (min-width: 768px) {
.dashboard-content {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
}
}
@media (max-width: 767px) {
.filter-section,
.campaign-section {
grid-column: 1 / -1;
}
.dashboard-cards {
grid-template-columns: repeat(2, 1fr);
}
.dashboard-data-selector {
grid-template-columns: repeat(1, 1fr);
}
}

View File

@@ -0,0 +1,273 @@
Vue.component('dashboard-location-selector', {
template: `
<div class="dashboard-data-selector">
<tt-select sm label="Netzbesitzer" :value="selectedNetworkOwner" :options="filterOptions.netOwners" @input="$emit('update:selectedNetworkOwner', $event)"/>
<tt-select sm label="Kampagne" :value="selectedCampaign" :options="filterOptions.campaigns" @input="$emit('update:selectedCampaign', $event)"/>
<tt-select sm label="Gemeinde" :value="selectedGemeinde" :options="filterOptions.gemeinden" @input="$emit('update:selectedGemeinde', $event)"/>
</div>
`,
props:
{
selectedNetworkOwner: {type: String, required: true},
selectedCampaign: {type: String, required: true},
selectedGemeinde: {type: String, required: true},
}
,
data() {
return {
filterOptions: {
netOwners: [],
campaigns: [],
gemeinden: [],
},
};
},
async mounted() {
await this.fetchNetOwnerFilterOptions();
await this.fetchCampaignFilterOptions();
await this.fetchCampaignGemeindeFilterOptions();
},
methods: {
async fetchNetOwnerFilterOptions() {
const response = await axios.get(`${window.TT_CONFIG['BASE_URL']}/getNetOwnerFilterOptions`);
this.filterOptions.netOwners = [{value: 'all', text: 'Alle'}, ...response.data];
},
async fetchCampaignFilterOptions() {
const response = await axios.post(`${window.TT_CONFIG['BASE_URL']}/getCampaignFilterOptions`, {netOwners: this.selectedNetworkOwner === 'all' ? undefined : this.selectedNetworkOwner});
this.filterOptions.campaigns = [{value: 'all', text: 'Alle'}, ...response.data];
},
async fetchCampaignGemeindeFilterOptions() {
const response = await axios.post(`${window.TT_CONFIG['BASE_URL']}/getCampaignGemeindeFilterOptions`,
{netOwners: this.selectedNetworkOwner === 'all' ? undefined : this.selectedNetworkOwner, campaigns: this.selectedCampaign === 'all' ? undefined : this.selectedCampaign});
this.filterOptions.gemeinden = [{value: 'all', text: 'Alle'}, ...response.data];
},
},
watch: {
selectedNetworkOwner() {
this.fetchCampaignFilterOptions();
this.fetchCampaignGemeindeFilterOptions();
},
selectedCampaign() {
this.fetchCampaignGemeindeFilterOptions();
}
},
});
Vue.component('tt-dashboard-display-card', {
props: ['header', 'icon', 'text', 'subHeaders', 'color'],
computed: {
cardHeight() {
return `${96 + 25 * this.subHeaders.length}px`;
}
},
template: `
<div class="card">
<div class="card-body p-0" :style="{ backgroundColor: color }">
<div :style="{ height: cardHeight }" class="p-2">
<div class="float-right">
<i :class="icon" class="text-white widget-icon font-24"></i>
</div>
<h5 class="text-white font-weight-normal mt-0">{{ header }}</h5>
<h3 class="mt-2 text-white">{{ text }}</h3>
<div class="text-white font-weight-light tt-dashboard-display-card-sub-header-container">
<p v-for="(subHeader, index) in subHeaders" :key="index" class="mb-0">{{ subHeader }}</p>
</div>
</div>
</div>
</div>
`
});
Vue.component('dashboard-new', {
template: `
<tt-card>
<tt-loader v-if="isLoading" style="margin: -1.5rem"/>
<div class="dashboard-new">
<dashboard-location-selector
:selectedNetworkOwner="selectedNetworkOwner"
:selectedCampaign="selectedCampaign"
:selectedGemeinde="selectedGemeinde"
@update:selectedNetworkOwner="selectedNetworkOwner = $event"
@update:selectedCampaign="selectedCampaign = $event"
@update:selectedGemeinde="selectedGemeinde = $event"
/>
<hr>
<h4 class="mt-4">Bestellungen</h4>
<div class="dashboard-cards position-relative">
<tt-dashboard-display-card
header="Bestellungen gesamt"
icon="fas fa-check"
:text="dashboardData.order_actual_order + ' / ' + dashboardData.order_max_home_addrdb"
:subHeaders="[
'Vollanschluss: ' + (parseInt(dashboardData.order_efh_vollanschluss) + parseInt(dashboardData.order_mph_vollanschluss)),
'Vorsorgeanschluss: ' + (parseInt(dashboardData.order_efh_vorsorge) + parseInt(dashboardData.order_mph_vorsorge))
]"
color="#28a745"
/>
<tt-dashboard-display-card
header="Bestellungen gesamt EFH"
icon="fas fa-home"
:text="dashboardData.order_efh"
:subHeaders="[
'Vollanschluss: ' + dashboardData.order_efh_vollanschluss,
'Vorsorgeanschluss: ' + dashboardData.order_efh_vorsorge
]"
color="#17a2b8"
/>
<tt-dashboard-display-card
header="Bestellungen gesamt MPH"
icon="fas fa-building"
:text="dashboardData.order_mph"
:subHeaders="[
'Vollanschluss: ' + dashboardData.order_mph_vollanschluss,
'Vorsorgeanschluss: ' + dashboardData.order_mph_vorsorge
]"
color="#dbab1a"
/>
</div>
<hr>
<h4 class="mt-4">Baufortschritt</h4>
<div class="dashboard-cards position-relative">
<tt-dashboard-display-card
header="140 - Leerrohr an der Grundstücksgrenze abgelegt"
icon="fas fa-road"
:text="dashboardData.baufortschritt_140 + ' / ' + dashboardData.order_actual_order"
:subHeaders="[]"
color="#6c757d"
/>
<tt-dashboard-display-card
header="Installationspaket erhalten"
icon="fas fa-box-open"
:text="dashboardData.installationspaket_erhalten + ' / ' + dashboardData.order_efh"
:subHeaders="[]"
color="#007bff"
/>
<tt-dashboard-display-card
header="Leerrohr im Haus"
icon="fas fa-house-user"
:text="dashboardData.lehrrohr_im_haus + ' / ' + dashboardData.order_efh"
:subHeaders="[]"
color="#ffc107"
/>
<tt-dashboard-display-card
header="Inhauskabel verlegt EFH"
icon="fas fa-plug"
:text="dashboardData.inhouse_kabel_verlegt_efh + ' / ' + dashboardData.order_efh"
:subHeaders="[]"
color="#28a745"
/>
<tt-dashboard-display-card
header="Inhauskabel verlegt MPH"
icon="fas fa-building-user"
:text="dashboardData.inhouse_kabel_verlegt_mph + ' / ' + dashboardData.order_mph"
:subHeaders="[]"
color="#17a2b8"
/>
</div>
<hr>
<h4 class="mt-4">Installationsfortschritt</h4>
<div class="dashboard-cards position-relative">
<tt-dashboard-display-card
header="245 - Installation freigegeben"
icon="fas fa-tools"
:text="dashboardData.installationsfortschritt_245 + ' / ' + dashboardData.order_actual_order"
:subHeaders="[]"
color="#6c757d"
/>
<tt-dashboard-display-card
header="300 - ONT installiert"
icon="fas fa-wifi"
:text="dashboardData.ont_installiert_300 + ' / ' + dashboardData.order_actual_order"
:subHeaders="[]"
color="#007bff"
/>
<tt-dashboard-display-card
header="350 - Vollanschluss dokumentiert (Vollanschluss)"
icon="fas fa-file-alt"
:text="dashboardData.vollanschluss_dokumentiert_350 + ' / ' + (parseInt(dashboardData.order_efh_vollanschluss) + parseInt(dashboardData.order_mph_vollanschluss))"
:subHeaders="[
'' // Add any additional info if needed.
] "
color="#ffc107"
/>
<tt-dashboard-display-card
header="351 - Vorsorgeanschluss dokumentiert (Vorsorgeanschluss)"
icon="fas fa-file-alt"
:text="dashboardData.vorsorge_dokumentiert_351 + ' / ' + (parseInt(dashboardData.order_efh_vorsorge) + parseInt(dashboardData.order_mph_vorsorge))"
:subHeaders="[]"
color="#ffc107"
/>
<tt-dashboard-display-card
header="500 - Provider bestellt"
icon="fas fa-shopping-cart"
:text="dashboardData.provider_bestellt_500 + ' / ' + dashboardData.order_actual_order"
:subHeaders="[]"
color="#28a745"
/>
</div>
</div>
</tt-card>
`,
data() {
return {
dashboardData: {
order_max_home_addrdb: 0,
order_actual_order: "0",
order_efh: "0",
order_efh_vorsorge: "0",
order_efh_vollanschluss: "0",
order_mph: "0",
order_mph_vorsorge: "0",
order_mph_vollanschluss: "0",
baufortschritt_140: "0",
installationspaket_erhalten: "0",
lehrrohr_im_haus: "0",
inhouse_kabel_verlegt_efh: "0",
inhouse_kabel_verlegt_mph: "0",
installationsfortschritt_245: "0",
ont_installiert_300: "0",
vollanschluss_dokumentiert_350: "0",
vorsorge_dokumentiert_351: "0",
provider_bestellt_500: "0"
},
selectedNetworkOwner: 'all',
selectedCampaign: 'all',
selectedGemeinde: 'all',
isLoading: false
};
},
async mounted() {
await this.fetchDashboardData();
},
methods: {
async fetchDashboardData() {
this.isLoading = true;
try {
const response = await axios.post(`${window.TT_CONFIG['BASE_URL']}/getDashboardData`, {
netOwners: this.selectedNetworkOwner === 'all' ? '' : this.selectedNetworkOwner,
campaigns: this.selectedCampaign === 'all' ? '' : this.selectedCampaign,
gemeinden: this.selectedGemeinde === 'all' ? '' : this.selectedGemeinde,
});
console.log(response.data);
this.dashboardData = response.data;
} catch (error) {
console.error('Error fetching dashboard data:', error);
} finally {
this.isLoading = false;
}
}
},
watch: {
selectedNetworkOwner() {
this.fetchDashboardData();
},
selectedCampaign() {
this.fetchDashboardData();
},
selectedGemeinde() {
this.fetchDashboardData();
}
}
});