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