Meter tyoe logic for projects in concentrators page
This commit is contained in:
@@ -170,6 +170,15 @@ export default function ConcentratorsPage() {
|
|||||||
projects={c.projectsData}
|
projects={c.projectsData}
|
||||||
onRefresh={c.loadConcentrators}
|
onRefresh={c.loadConcentrators}
|
||||||
refreshDisabled={c.loadingProjects}
|
refreshDisabled={c.loadingProjects}
|
||||||
|
meterTypes={c.meterTypes}
|
||||||
|
selectedMeterTypeId={c.selectedMeterTypeId}
|
||||||
|
onSelectMeterTypeId={(id: string) => {
|
||||||
|
c.setSelectedMeterTypeId(id);
|
||||||
|
c.setSelectedProject("");
|
||||||
|
setActiveConcentrator(null);
|
||||||
|
setSearch("");
|
||||||
|
}}
|
||||||
|
loadingMeterTypes={c.loadingMeterTypes}
|
||||||
/>
|
/>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
@@ -181,7 +190,9 @@ export default function ConcentratorsPage() {
|
|||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold">Concentrator Management</h1>
|
<h1 className="text-2xl font-bold">Concentrator Management</h1>
|
||||||
<p className="text-sm text-blue-100">
|
<p className="text-sm text-blue-100">
|
||||||
{c.selectedProject
|
{c.projectsData.length === 0 && c.selectedMeterTypeId
|
||||||
|
? `No hay proyectos disponibles con el filtro seleccionado`
|
||||||
|
: c.selectedProject
|
||||||
? `Proyecto: ${c.selectedProject} • Tipo: ${c.sampleViewLabel}`
|
? `Proyecto: ${c.selectedProject} • Tipo: ${c.sampleViewLabel}`
|
||||||
: "Selecciona un proyecto desde el panel izquierdo"}
|
: "Selecciona un proyecto desde el panel izquierdo"}
|
||||||
</p>
|
</p>
|
||||||
@@ -190,7 +201,7 @@ export default function ConcentratorsPage() {
|
|||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<button
|
<button
|
||||||
onClick={openCreateModal}
|
onClick={openCreateModal}
|
||||||
disabled={c.allProjects.length === 0}
|
disabled={c.allProjects.length === 0 || c.projectsData.length === 0}
|
||||||
className="flex items-center gap-2 px-4 py-2 bg-white text-[#4c5f9e] rounded-lg disabled:opacity-50 disabled:cursor-not-allowed"
|
className="flex items-center gap-2 px-4 py-2 bg-white text-[#4c5f9e] rounded-lg disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
<Plus size={16} /> Agregar
|
<Plus size={16} /> Agregar
|
||||||
@@ -214,6 +225,7 @@ export default function ConcentratorsPage() {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={c.loadConcentrators}
|
onClick={c.loadConcentrators}
|
||||||
|
disabled={c.projectsData.length === 0}
|
||||||
className="flex items-center gap-2 px-4 py-2 border border-white/40 rounded-lg disabled:opacity-60"
|
className="flex items-center gap-2 px-4 py-2 border border-white/40 rounded-lg disabled:opacity-60"
|
||||||
>
|
>
|
||||||
<RefreshCcw size={16} /> Actualizar
|
<RefreshCcw size={16} /> Actualizar
|
||||||
@@ -222,11 +234,11 @@ export default function ConcentratorsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
className="bg-white rounded-lg shadow px-4 py-2 text-sm"
|
className="bg-white rounded-lg shadow px-4 py-2 text-sm disabled:opacity-60 disabled:cursor-not-allowed"
|
||||||
placeholder="Buscar concentrador..."
|
placeholder="Buscar concentrador..."
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
disabled={!c.selectedProject}
|
disabled={!c.selectedProject || c.projectsData.length === 0}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={!c.selectedProject ? "opacity-60 pointer-events-none" : ""}>
|
<div className={!c.selectedProject ? "opacity-60 pointer-events-none" : ""}>
|
||||||
@@ -236,7 +248,11 @@ export default function ConcentratorsPage() {
|
|||||||
activeRowId={activeConcentrator?.id}
|
activeRowId={activeConcentrator?.id}
|
||||||
onRowClick={(row) => setActiveConcentrator(row)}
|
onRowClick={(row) => setActiveConcentrator(row)}
|
||||||
emptyMessage={
|
emptyMessage={
|
||||||
!c.selectedProject
|
c.projectsData.length === 0 && c.selectedMeterTypeId
|
||||||
|
? `No hay proyectos con el tipo de toma seleccionado (${
|
||||||
|
c.meterTypes.find((mt) => mt.id === c.selectedMeterTypeId)?.name ?? "desconocido"
|
||||||
|
}) que tengan concentradores.`
|
||||||
|
: !c.selectedProject
|
||||||
? "Selecciona un proyecto para ver los concentradores."
|
? "Selecciona un proyecto para ver los concentradores."
|
||||||
: c.loadingConcentrators
|
: c.loadingConcentrators
|
||||||
? "Cargando concentradores..."
|
? "Cargando concentradores..."
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
// src/pages/concentrators/ConcentratorsSidebar.tsx
|
// src/pages/concentrators/ConcentratorsSidebar.tsx
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { ChevronDown, Check, RefreshCcw } from "lucide-react";
|
import { Check, RefreshCcw } from "lucide-react";
|
||||||
import type { ProjectCard, SampleView } from "./ConcentratorsPage";
|
import type { ProjectCard, SampleView } from "./ConcentratorsPage";
|
||||||
|
import type { MeterType } from "../../api/meterTypes";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
loadingProjects: boolean;
|
loadingProjects: boolean;
|
||||||
@@ -23,6 +24,11 @@ type Props = {
|
|||||||
|
|
||||||
onRefresh: () => void;
|
onRefresh: () => void;
|
||||||
refreshDisabled: boolean;
|
refreshDisabled: boolean;
|
||||||
|
|
||||||
|
meterTypes: MeterType[];
|
||||||
|
selectedMeterTypeId: string;
|
||||||
|
onSelectMeterTypeId: (id: string) => void;
|
||||||
|
loadingMeterTypes: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ConcentratorsSidebar({
|
export default function ConcentratorsSidebar({
|
||||||
@@ -30,13 +36,16 @@ export default function ConcentratorsSidebar({
|
|||||||
sampleView,
|
sampleView,
|
||||||
sampleViewLabel,
|
sampleViewLabel,
|
||||||
typesMenuOpen,
|
typesMenuOpen,
|
||||||
setTypesMenuOpen,
|
|
||||||
onChangeSampleView,
|
onChangeSampleView,
|
||||||
selectedProject,
|
selectedProject,
|
||||||
onSelectProject,
|
onSelectProject,
|
||||||
projects,
|
projects,
|
||||||
onRefresh,
|
onRefresh,
|
||||||
refreshDisabled,
|
refreshDisabled,
|
||||||
|
meterTypes,
|
||||||
|
selectedMeterTypeId,
|
||||||
|
onSelectMeterTypeId,
|
||||||
|
loadingMeterTypes,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const options = useMemo(
|
const options = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -76,24 +85,7 @@ export default function ConcentratorsSidebar({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tipos de tomas (dropdown) */}
|
|
||||||
<div className="mt-4 relative">
|
<div className="mt-4 relative">
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setTypesMenuOpen((v) => !v)}
|
|
||||||
className="w-full inline-flex items-center justify-between rounded-xl border border-gray-200 bg-white px-3 py-2 text-sm font-semibold text-gray-700 shadow-sm hover:bg-gray-50"
|
|
||||||
>
|
|
||||||
<span className="flex items-center gap-2">
|
|
||||||
Tipos de tomas
|
|
||||||
<span className="text-xs font-semibold text-gray-500">
|
|
||||||
({sampleViewLabel})
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<ChevronDown
|
|
||||||
size={16}
|
|
||||||
className={`${typesMenuOpen ? "rotate-180" : ""} transition`}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{typesMenuOpen && (
|
{typesMenuOpen && (
|
||||||
<div className="absolute z-50 mt-2 w-full rounded-xl border border-gray-200 bg-white shadow-lg overflow-hidden">
|
<div className="absolute z-50 mt-2 w-full rounded-xl border border-gray-200 bg-white shadow-lg overflow-hidden">
|
||||||
@@ -124,15 +116,36 @@ export default function ConcentratorsSidebar({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-3">
|
||||||
|
<label className="block text-xs font-semibold text-gray-700 mb-1.5">
|
||||||
|
Filtrar por Tipo de Toma
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={selectedMeterTypeId}
|
||||||
|
onChange={(e) => onSelectMeterTypeId(e.target.value)}
|
||||||
|
disabled={loadingMeterTypes}
|
||||||
|
className="w-full rounded-lg border border-gray-200 bg-white px-3 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 disabled:opacity-60 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
<option value="">Todos los tipos de toma</option>
|
||||||
|
{meterTypes.map((type) => (
|
||||||
|
<option key={type.id} value={type.id}>
|
||||||
|
{type.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* List */}
|
{/* List */}
|
||||||
<div className="mt-4 overflow-y-auto flex-1 space-y-3 pr-1">
|
<div className="mt-4 overflow-y-auto flex-1 space-y-3 pr-1">
|
||||||
{loadingProjects ? (
|
{loadingProjects ? (
|
||||||
<div className="text-sm text-gray-500">Loading projects...</div>
|
<div className="text-sm text-gray-500">Loading projects...</div>
|
||||||
) : projects.length === 0 ? (
|
) : projects.length === 0 ? (
|
||||||
<div className="text-sm text-gray-500">
|
<div className="text-sm text-gray-500">
|
||||||
{sampleView === "GENERAL"
|
{selectedMeterTypeId
|
||||||
? "No projects available. Please contact your administrator."
|
? `No hay proyectos con el tipo de toma seleccionado y concentradores ${sampleViewLabel}.`
|
||||||
: `No projects with ${sampleViewLabel} concentrators found.`
|
: sampleView === "GENERAL"
|
||||||
|
? "No hay proyectos con concentradores disponibles."
|
||||||
|
: `No hay proyectos con concentradores ${sampleViewLabel}.`
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
type ConcentratorType,
|
type ConcentratorType,
|
||||||
} from "../../api/concentrators";
|
} from "../../api/concentrators";
|
||||||
import { fetchProjects, type Project } from "../../api/projects";
|
import { fetchProjects, type Project } from "../../api/projects";
|
||||||
|
import { fetchMeterTypes, type MeterType } from "../../api/meterTypes";
|
||||||
import type { ProjectCard, SampleView } from "./ConcentratorsPage";
|
import type { ProjectCard, SampleView } from "./ConcentratorsPage";
|
||||||
|
|
||||||
type User = {
|
type User = {
|
||||||
@@ -17,11 +18,15 @@ export function useConcentrators(currentUser: User) {
|
|||||||
|
|
||||||
const [loadingProjects, setLoadingProjects] = useState(true);
|
const [loadingProjects, setLoadingProjects] = useState(true);
|
||||||
const [loadingConcentrators, setLoadingConcentrators] = useState(true);
|
const [loadingConcentrators, setLoadingConcentrators] = useState(true);
|
||||||
|
const [loadingMeterTypes, setLoadingMeterTypes] = useState(true);
|
||||||
|
|
||||||
const [projects, setProjects] = useState<Project[]>([]);
|
const [projects, setProjects] = useState<Project[]>([]);
|
||||||
const [allProjects, setAllProjects] = useState<string[]>([]);
|
const [allProjects, setAllProjects] = useState<string[]>([]);
|
||||||
const [selectedProject, setSelectedProject] = useState("");
|
const [selectedProject, setSelectedProject] = useState("");
|
||||||
|
|
||||||
|
const [meterTypes, setMeterTypes] = useState<MeterType[]>([]);
|
||||||
|
const [selectedMeterTypeId, setSelectedMeterTypeId] = useState<string>("");
|
||||||
|
|
||||||
const [concentrators, setConcentrators] = useState<Concentrator[]>([]);
|
const [concentrators, setConcentrators] = useState<Concentrator[]>([]);
|
||||||
const [filteredConcentrators, setFilteredConcentrators] = useState<
|
const [filteredConcentrators, setFilteredConcentrators] = useState<
|
||||||
Concentrator[]
|
Concentrator[]
|
||||||
@@ -54,6 +59,19 @@ export function useConcentrators(currentUser: User) {
|
|||||||
[allProjects, currentUser.role, currentUser.project]
|
[allProjects, currentUser.role, currentUser.project]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const loadMeterTypes = async () => {
|
||||||
|
setLoadingMeterTypes(true);
|
||||||
|
try {
|
||||||
|
const meterTypesData = await fetchMeterTypes();
|
||||||
|
setMeterTypes(meterTypesData);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error loading meter types:", err);
|
||||||
|
setMeterTypes([]);
|
||||||
|
} finally {
|
||||||
|
setLoadingMeterTypes(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const loadProjects = async () => {
|
const loadProjects = async () => {
|
||||||
setLoadingProjects(true);
|
setLoadingProjects(true);
|
||||||
try {
|
try {
|
||||||
@@ -92,8 +110,8 @@ export function useConcentrators(currentUser: User) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// init - load projects and concentrators
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
loadMeterTypes();
|
||||||
loadProjects();
|
loadProjects();
|
||||||
loadConcentrators();
|
loadConcentrators();
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@@ -163,7 +181,21 @@ export function useConcentrators(currentUser: User) {
|
|||||||
const baseContact = "Operaciones";
|
const baseContact = "Operaciones";
|
||||||
const baseLastSync = "Hace 1 h";
|
const baseLastSync = "Hace 1 h";
|
||||||
|
|
||||||
return visibleProjects.map((projectId) => ({
|
let filteredProjects = visibleProjects;
|
||||||
|
|
||||||
|
filteredProjects = filteredProjects.filter((projectId) => {
|
||||||
|
const count = counts[projectId] ?? 0;
|
||||||
|
return count > 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selectedMeterTypeId) {
|
||||||
|
filteredProjects = filteredProjects.filter((projectId) => {
|
||||||
|
const project = projects.find((p) => p.id === projectId);
|
||||||
|
return project?.meterTypeId === selectedMeterTypeId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredProjects.map((projectId) => ({
|
||||||
id: projectId,
|
id: projectId,
|
||||||
name: projectNameMap[projectId] ?? projectId,
|
name: projectNameMap[projectId] ?? projectId,
|
||||||
region: baseRegion,
|
region: baseRegion,
|
||||||
@@ -174,12 +206,27 @@ export function useConcentrators(currentUser: User) {
|
|||||||
contact: baseContact,
|
contact: baseContact,
|
||||||
status: "ACTIVO" as const,
|
status: "ACTIVO" as const,
|
||||||
}));
|
}));
|
||||||
}, [concentrators, visibleProjects, projects, isGeneral, sampleView]);
|
}, [concentrators, visibleProjects, projects, isGeneral, sampleView, selectedMeterTypeId]);
|
||||||
|
|
||||||
const projectsData: ProjectCard[] = useMemo(() => {
|
const projectsData: ProjectCard[] = useMemo(() => {
|
||||||
return projectsDataGeneral;
|
return projectsDataGeneral;
|
||||||
}, [projectsDataGeneral]);
|
}, [projectsDataGeneral]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (projectsData.length > 0) {
|
||||||
|
const firstProject = projectsData[0];
|
||||||
|
const currentProjectExists = projectsData.find((p) => p.id === selectedProject);
|
||||||
|
|
||||||
|
if (!selectedProject || !currentProjectExists) {
|
||||||
|
setSelectedProject(firstProject.id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (selectedProject) {
|
||||||
|
setSelectedProject("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [projectsData]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// view
|
// view
|
||||||
sampleView,
|
sampleView,
|
||||||
@@ -190,6 +237,7 @@ export function useConcentrators(currentUser: User) {
|
|||||||
// loading
|
// loading
|
||||||
loadingProjects,
|
loadingProjects,
|
||||||
loadingConcentrators,
|
loadingConcentrators,
|
||||||
|
loadingMeterTypes,
|
||||||
|
|
||||||
// projects
|
// projects
|
||||||
allProjects,
|
allProjects,
|
||||||
@@ -198,6 +246,10 @@ export function useConcentrators(currentUser: User) {
|
|||||||
selectedProject,
|
selectedProject,
|
||||||
setSelectedProject,
|
setSelectedProject,
|
||||||
|
|
||||||
|
meterTypes,
|
||||||
|
selectedMeterTypeId,
|
||||||
|
setSelectedMeterTypeId,
|
||||||
|
|
||||||
// data
|
// data
|
||||||
concentrators,
|
concentrators,
|
||||||
setConcentrators,
|
setConcentrators,
|
||||||
|
|||||||
Reference in New Issue
Block a user