Skip to content

Commit e4bc931

Browse files
committed
feat: create Animal Management landing page with dashboard, section navbar, and animal inventory grid
1 parent 6c9a0cc commit e4bc931

4 files changed

Lines changed: 743 additions & 0 deletions

File tree

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
import React, { useState, useEffect } from 'react';
2+
import styles from './AnimalManagement.module.css';
3+
4+
// --- Hook to detect dark mode from the app's theme ---
5+
function useDarkMode() {
6+
const [darkMode, setDarkMode] = useState(false);
7+
8+
useEffect(() => {
9+
const checkDarkMode = () => {
10+
const isDark =
11+
document.body.classList.contains('dark-mode') ||
12+
document.body.getAttribute('data-theme') === 'dark' ||
13+
document.documentElement.classList.contains('dark-mode') ||
14+
window.matchMedia('(prefers-color-scheme: dark)').matches;
15+
setDarkMode(isDark);
16+
};
17+
18+
checkDarkMode();
19+
20+
// Watch for class changes on body (theme toggles)
21+
const observer = new MutationObserver(checkDarkMode);
22+
observer.observe(document.body, { attributes: true, attributeFilter: ['class', 'data-theme'] });
23+
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
24+
25+
// Watch for system theme changes
26+
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
27+
mediaQuery.addEventListener('change', checkDarkMode);
28+
29+
return () => {
30+
observer.disconnect();
31+
mediaQuery.removeEventListener('change', checkDarkMode);
32+
};
33+
}, []);
34+
35+
return darkMode;
36+
}
37+
38+
// --- Mock Data ---
39+
const dashboardStats = [
40+
{ id: 1, label: 'Total Animals', value: 148, icon: '🐄', color: 'green' },
41+
{ id: 2, label: 'Pending Animal Orders', value: 12, icon: '📋', color: 'amber' },
42+
{ id: 3, label: 'Feed Orders', value: 7, icon: '🌾', color: 'brown' },
43+
{ id: 4, label: 'Upcoming Culling Tasks', value: 3, icon: '📅', color: 'red' },
44+
];
45+
46+
const sectionTabs = [
47+
'Animal Inventory',
48+
'Animal Orders',
49+
'Feed Inventory',
50+
'Feed Orders',
51+
'Culling Events',
52+
];
53+
54+
const animals = [
55+
{
56+
id: 1,
57+
name: 'Holstein Cows',
58+
type: 'Holstein Friesian',
59+
purpose: 'Dairy',
60+
count: 24,
61+
location: 'Barn A - North Pasture',
62+
age: '2–5 years',
63+
health: 'Healthy',
64+
},
65+
{
66+
id: 2,
67+
name: 'Leghorn Chickens',
68+
type: 'White Leghorn',
69+
purpose: 'Egg Laying',
70+
count: 60,
71+
location: 'Coop 1',
72+
age: '6–18 months',
73+
health: 'Healthy',
74+
},
75+
{
76+
id: 3,
77+
name: 'Boer Goats',
78+
type: 'Boer',
79+
purpose: 'Meat',
80+
count: 18,
81+
location: 'Pen B - South Field',
82+
age: '1–3 years',
83+
health: 'Attention',
84+
},
85+
{
86+
id: 4,
87+
name: 'Berkshire Pigs',
88+
type: 'Berkshire',
89+
purpose: 'Meat',
90+
count: 15,
91+
location: 'Sty 2',
92+
age: '8–14 months',
93+
health: 'Healthy',
94+
},
95+
{
96+
id: 5,
97+
name: 'Pekin Ducks',
98+
type: 'Pekin',
99+
purpose: 'Egg Laying / Meat',
100+
count: 20,
101+
location: 'Pond Enclosure',
102+
age: '4–12 months',
103+
health: 'Healthy',
104+
},
105+
{
106+
id: 6,
107+
name: 'Merino Sheep',
108+
type: 'Merino',
109+
purpose: 'Wool / Meat',
110+
count: 11,
111+
location: 'Barn B - East Pasture',
112+
age: '1–4 years',
113+
health: 'Critical',
114+
},
115+
];
116+
117+
// --- Sub-components ---
118+
119+
function DashboardCard({ stat, darkMode }) {
120+
return (
121+
<div
122+
className={`${styles.dashboardCard} ${styles[`card_${stat.color}`]} ${
123+
darkMode ? styles.dark : ''
124+
}`}
125+
>
126+
<div className={styles.cardIcon}>{stat.icon}</div>
127+
<div className={styles.cardContent}>
128+
<span className={styles.cardValue}>{stat.value}</span>
129+
<span className={styles.cardLabel}>{stat.label}</span>
130+
</div>
131+
</div>
132+
);
133+
}
134+
135+
function HealthTag({ status, darkMode }) {
136+
const statusClass = status.toLowerCase().replace(' ', '');
137+
return (
138+
<span
139+
className={`${styles.healthTag} ${styles[`health_${statusClass}`]} ${
140+
darkMode ? styles.dark : ''
141+
}`}
142+
>
143+
{status}
144+
</span>
145+
);
146+
}
147+
148+
function AnimalCard({ animal, darkMode }) {
149+
return (
150+
<div className={`${styles.animalCard} ${darkMode ? styles.dark : ''}`}>
151+
<div className={styles.animalCardHeader}>
152+
<h3 className={styles.animalName}>{animal.name}</h3>
153+
<HealthTag status={animal.health} darkMode={darkMode} />
154+
</div>
155+
<div className={styles.animalDetails}>
156+
<div className={styles.detailRow}>
157+
<span className={styles.detailLabel}>🔢 Count</span>
158+
<span className={styles.detailValue}>{animal.count}</span>
159+
</div>
160+
<div className={styles.detailRow}>
161+
<span className={styles.detailLabel}>🎯 Purpose</span>
162+
<span className={styles.detailValue}>{animal.purpose}</span>
163+
</div>
164+
<div className={styles.detailRow}>
165+
<span className={styles.detailLabel}>🧬 Type / Breed</span>
166+
<span className={styles.detailValue}>{animal.type}</span>
167+
</div>
168+
<div className={styles.detailRow}>
169+
<span className={styles.detailLabel}>📍 Location</span>
170+
<span className={styles.detailValue}>{animal.location}</span>
171+
</div>
172+
<div className={styles.detailRow}>
173+
<span className={styles.detailLabel}>📅 Age</span>
174+
<span className={styles.detailValue}>{animal.age}</span>
175+
</div>
176+
</div>
177+
<button className={`${styles.viewDetailsBtn} ${darkMode ? styles.dark : ''}`} type="button">
178+
View Details
179+
</button>
180+
</div>
181+
);
182+
}
183+
184+
function SectionNavbar({ activeSection, onSectionChange, darkMode }) {
185+
return (
186+
<nav className={`${styles.sectionNavbar} ${darkMode ? styles.dark : ''}`}>
187+
{sectionTabs.map(tab => (
188+
<button
189+
key={tab}
190+
type="button"
191+
className={`${styles.sectionTab} ${activeSection === tab ? styles.activeTab : ''} ${
192+
darkMode ? styles.dark : ''
193+
}`}
194+
onClick={() => onSectionChange(tab)}
195+
>
196+
{tab}
197+
</button>
198+
))}
199+
</nav>
200+
);
201+
}
202+
203+
function SearchAndFilter({ searchTerm, onSearchChange, darkMode }) {
204+
return (
205+
<div className={`${styles.searchBar} ${darkMode ? styles.dark : ''}`}>
206+
<input
207+
type="text"
208+
placeholder="Search animals by name, breed, or location..."
209+
value={searchTerm}
210+
onChange={e => onSearchChange(e.target.value)}
211+
className={`${styles.searchInput} ${darkMode ? styles.dark : ''}`}
212+
aria-label="Search animals"
213+
/>
214+
</div>
215+
);
216+
}
217+
218+
// --- Main Component ---
219+
220+
function AnimalManagement() {
221+
const darkMode = useDarkMode();
222+
const [activeSection, setActiveSection] = useState('Animal Inventory');
223+
const [searchTerm, setSearchTerm] = useState('');
224+
225+
const filteredAnimals = animals.filter(animal => {
226+
const term = searchTerm.toLowerCase();
227+
return (
228+
animal.name.toLowerCase().includes(term) ||
229+
animal.type.toLowerCase().includes(term) ||
230+
animal.location.toLowerCase().includes(term) ||
231+
animal.purpose.toLowerCase().includes(term)
232+
);
233+
});
234+
235+
return (
236+
<div className={`${styles.container} ${darkMode ? styles.dark : ''}`}>
237+
{/* Page Header */}
238+
<header className={styles.pageHeader}>
239+
<h1 className={`${styles.pageTitle} ${darkMode ? styles.dark : ''}`}>
240+
🐾 Animal Management
241+
</h1>
242+
<p className={`${styles.pageSubtitle} ${darkMode ? styles.dark : ''}`}>
243+
Monitor animal inventory, orders, feed, and culling events.
244+
</p>
245+
</header>
246+
247+
{/* Dashboard Cards */}
248+
<section className={styles.dashboardGrid} aria-label="Dashboard summary">
249+
{dashboardStats.map(stat => (
250+
<DashboardCard key={stat.id} stat={stat} darkMode={darkMode} />
251+
))}
252+
</section>
253+
254+
{/* Section Navbar */}
255+
<SectionNavbar
256+
activeSection={activeSection}
257+
onSectionChange={setActiveSection}
258+
darkMode={darkMode}
259+
/>
260+
261+
{/* Active Section Content */}
262+
{activeSection === 'Animal Inventory' && (
263+
<section aria-label="Animal Inventory">
264+
<SearchAndFilter
265+
searchTerm={searchTerm}
266+
onSearchChange={setSearchTerm}
267+
darkMode={darkMode}
268+
/>
269+
{filteredAnimals.length > 0 ? (
270+
<div className={styles.animalGrid}>
271+
{filteredAnimals.map(animal => (
272+
<AnimalCard key={animal.id} animal={animal} darkMode={darkMode} />
273+
))}
274+
</div>
275+
) : (
276+
<p className={`${styles.noResults} ${darkMode ? styles.dark : ''}`}>
277+
No animals match your search.
278+
</p>
279+
)}
280+
</section>
281+
)}
282+
283+
{activeSection !== 'Animal Inventory' && (
284+
<div className={`${styles.placeholderSection} ${darkMode ? styles.dark : ''}`}>
285+
<p>{activeSection} section coming soon.</p>
286+
</div>
287+
)}
288+
</div>
289+
);
290+
}
291+
292+
export default AnimalManagement;

0 commit comments

Comments
 (0)