feat: visual plant floor
diff --git a/erpnext/public/js/erpnext.bundle.js b/erpnext/public/js/erpnext.bundle.js
index dee9a06..b847e57 100644
--- a/erpnext/public/js/erpnext.bundle.js
+++ b/erpnext/public/js/erpnext.bundle.js
@@ -5,6 +5,8 @@
import "./utils/party";
import "./controllers/stock_controller";
import "./payment/payments";
+import "./templates/visual_plant_floor_template.html";
+import "./plant_floor_visual/visual_plant";
import "./controllers/taxes_and_totals";
import "./controllers/transaction";
import "./templates/item_selector.html";
diff --git a/erpnext/public/js/plant_floor_visual/visual_plant.js b/erpnext/public/js/plant_floor_visual/visual_plant.js
new file mode 100644
index 0000000..b1d120f
--- /dev/null
+++ b/erpnext/public/js/plant_floor_visual/visual_plant.js
@@ -0,0 +1,157 @@
+class VisualPlantFloor {
+ constructor({wrapper, skip_filters=false, plant_floor=null}, page=null) {
+ this.wrapper = wrapper;
+ this.plant_floor = plant_floor;
+ this.skip_filters = skip_filters;
+
+ this.make();
+ if (!this.skip_filters) {
+ this.page = page;
+ this.add_filter();
+ this.prepare_menu();
+ }
+ }
+
+ make() {
+ this.wrapper.append(`
+ <div class="plant-floor">
+ <div class="plant-floor-filter">
+ </div>
+ <div class="plant-floor-container col-sm-12">
+ </div>
+ </div>
+ `);
+
+ if (!this.skip_filters) {
+ this.filter_wrapper = this.wrapper.find('.plant-floor-filter');
+ this.visualization_wrapper = this.wrapper.find('.plant-floor-visualization');
+ } else if(this.plant_floor) {
+ this.prepare_data();
+ }
+ }
+
+ prepare_data() {
+ frappe.call({
+ method: 'erpnext.manufacturing.doctype.workstation.workstation.get_workstations',
+ args: {
+ plant_floor: this.plant_floor,
+ },
+ callback: (r) => {
+ this.workstations = r.message;
+ this.render_workstations();
+ }
+ });
+ }
+
+ add_filter() {
+ this.plant_floor = frappe.ui.form.make_control({
+ df: {
+ fieldtype: 'Link',
+ options: 'Plant Floor',
+ fieldname: 'plant_floor',
+ label: __('Plant Floor'),
+ reqd: 1,
+ onchange: () => {
+ this.render_plant_visualization();
+ }
+ },
+ parent: this.filter_wrapper,
+ render_input: true,
+ });
+
+ this.plant_floor.$wrapper.addClass('form-column col-sm-2');
+
+ this.workstation_type = frappe.ui.form.make_control({
+ df: {
+ fieldtype: 'Link',
+ options: 'Workstation Type',
+ fieldname: 'workstation_type',
+ label: __('Machine Type'),
+ onchange: () => {
+ this.render_plant_visualization();
+ }
+ },
+ parent: this.filter_wrapper,
+ render_input: true,
+ });
+
+ this.workstation_type.$wrapper.addClass('form-column col-sm-2');
+
+ this.workstation = frappe.ui.form.make_control({
+ df: {
+ fieldtype: 'Link',
+ options: 'Workstation',
+ fieldname: 'workstation',
+ label: __('Machine'),
+ onchange: () => {
+ this.render_plant_visualization();
+ },
+ get_query: () => {
+ if (this.workstation_type.get_value()) {
+ return {
+ filters: {
+ 'workstation_type': this.workstation_type.get_value() || ''
+ }
+ }
+ }
+ }
+ },
+ parent: this.filter_wrapper,
+ render_input: true,
+ });
+
+ this.workstation.$wrapper.addClass('form-column col-sm-2');
+
+ this.workstation_status = frappe.ui.form.make_control({
+ df: {
+ fieldtype: 'Select',
+ options: '\nProduction\nOff\nIdle\nProblem\nMaintenance\nSetup',
+ fieldname: 'workstation_status',
+ label: __('Status'),
+ onchange: () => {
+ this.render_plant_visualization();
+ },
+ },
+ parent: this.filter_wrapper,
+ render_input: true,
+ });
+ }
+
+ render_plant_visualization() {
+ let plant_floor = this.plant_floor.get_value();
+
+ if (plant_floor) {
+ frappe.call({
+ method: 'erpnext.manufacturing.doctype.workstation.workstation.get_workstations',
+ args: {
+ plant_floor: plant_floor,
+ workstation_type: this.workstation_type.get_value(),
+ workstation: this.workstation.get_value(),
+ workstation_status: this.workstation_status.get_value()
+ },
+ callback: (r) => {
+ this.workstations = r.message;
+ this.render_workstations();
+ }
+ });
+ }
+ }
+
+ render_workstations() {
+ console.log(this.wrapper.find('.plant-floor-container'))
+ this.wrapper.find('.plant-floor-container').empty();
+ let template = frappe.render_template("visual_plant_floor_template", {
+ workstations: this.workstations
+ });
+
+ $(template).appendTo(this.wrapper.find('.plant-floor-container'));
+ }
+
+ prepare_menu() {
+ this.page.add_menu_item(__('Refresh'), () => {
+ this.render_plant_visualization();
+ });
+ }
+}
+
+frappe.ui.VisualPlantFloor = VisualPlantFloor;
\ No newline at end of file
diff --git a/erpnext/public/js/templates/visual_plant_floor_template.html b/erpnext/public/js/templates/visual_plant_floor_template.html
new file mode 100644
index 0000000..2e67085
--- /dev/null
+++ b/erpnext/public/js/templates/visual_plant_floor_template.html
@@ -0,0 +1,19 @@
+{% $.each(workstations, (idx, row) => { %}
+ <div class="workstation-wrapper">
+ <div class="workstation-image">
+ <div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
+ <a class="workstation-image-link" href="{{row.workstation_link}}">
+ {% if(row.status_image) { %}
+ <img class="workstation-image-cls" src="{{row.status_image}}">
+ {% } else { %}
+ <div class="workstation-image-cls workstation-abbr">{{frappe.get_abbr(row.name, 2)}}</div>
+ {% } %}
+ </a>
+ </div>
+ </div>
+ <div class="workstation-card text-center">
+ <p style="background-color:{{row.background_color}};color:#fff">{{row.status}}</p>
+ <div>{{row.workstation_name}}</div>
+ </div>
+ </div>
+{% }); %}
\ No newline at end of file
diff --git a/erpnext/public/scss/erpnext.scss b/erpnext/public/scss/erpnext.scss
index 8ab5973..ef09854 100644
--- a/erpnext/public/scss/erpnext.scss
+++ b/erpnext/public/scss/erpnext.scss
@@ -490,3 +490,54 @@
.exercise-col {
padding: 10px;
}
+
+.plant-floor, .workstation-wrapper, .workstation-card p {
+ border-radius: var(--border-radius-md);
+ border: 1px solid var(--border-color);
+ box-shadow: none;
+ background-color: var(--card-bg);
+ position: relative;
+}
+
+.plant-floor {
+ padding-bottom: 25px;
+}
+
+.plant-floor-filter {
+ padding-top: 10px;
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.plant-floor-container {
+ padding-top: 10px;
+ display: grid;
+ grid-template-columns: repeat(6,minmax(0,1fr));
+ gap: var(--margin-xl);
+}
+
+@media screen and (max-width: 620px) {
+ .plant-floor-container {
+ grid-template-columns: repeat(2,minmax(0,1fr));
+ }
+}
+
+.plant-floor-container .workstation-card {
+ padding: 5px;
+}
+
+.plant-floor-container .workstation-image-link {
+ width: 100%;
+ font-size: 50px;
+ margin: var(--margin-sm);
+ min-height: 11rem;
+}
+
+.workstation-abbr {
+ display: flex;
+ background-color: var(--control-bg);
+ height:100%;
+ width:100%;
+ align-items: center;
+ justify-content: center;
+}
\ No newline at end of file