```react
import React, { useState, useEffect, useMemo } from 'react';
import { initializeApp } from 'firebase/app';
import { getAuth, signInAnonymously, onAuthStateChanged, signInWithCustomToken } from 'firebase/auth';
import { getFirestore, collection, onSnapshot, addDoc, serverTimestamp } from 'firebase/firestore';
import { Settings, BarChart3, Clock, Calendar, Download, LogOut, PlusCircle, Factory, User, CheckCircle2 } from 'lucide-react';
// --- Firebase Initialization ---
const firebaseConfig = typeof __firebase_config !== 'undefined' ? JSON.parse(__firebase_config) : {};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
const appId = typeof __app_id !== 'undefined' ? __app_id : 'loom-system-app';
// --- Helper Functions for Time & Shifts ---
const getProductionInfo = (inputDate = new Date()) => {
const d = new Date(inputDate);
const hours = d.getHours();
// If time is between midnight and 7 AM, it belongs to yesterday's production day
if (hours < 7) {
d.setDate(d.getDate() - 1);
}
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
const shift = (hours >= 7 && hours < 19) ? 'Day' : 'Night';
return {
dateString: `${year}-${month}-${day}`,
shift: shift
};
};
const DAY_SLOTS = ["07:00 - 09:00", "09:00 - 11:00", "11:00 - 13:00", "13:00 - 15:00", "15:00 - 17:00", "17:00 - 19:00"];
const NIGHT_SLOTS = ["19:00 - 21:00", "21:00 - 23:00", "23:00 - 01:00", "01:00 - 03:00", "03:00 - 05:00", "05:00 - 07:00"];
export default function App() {
const [fbUser, setFbUser] = useState(null);
const [userRole, setUserRole] = useState(null); // 'admin' or 'user'
const [records, setRecords] = useState([]);
const [loading, setLoading] = useState(true);
// --- Firebase Auth & Data Fetching ---
useEffect(() => {
const initAuth = async () => {
try {
if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) {
await signInWithCustomToken(auth, __initial_auth_token);
} else {
await signInAnonymously(auth);
}
} catch (err) {
console.error("Auth error:", err);
}
};
initAuth();
const unsubscribeAuth = onAuthStateChanged(auth, (user) => {
setFbUser(user);
});
return () => unsubscribeAuth();
}, []);
useEffect(() => {
if (!fbUser) return;
const recordsRef = collection(db, 'artifacts', appId, 'public', 'data', 'production_logs');
const unsubscribeData = onSnapshot(recordsRef,
(snapshot) => {
const data = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
// Sort by timestamp descending on client side
data.sort((a, b) => (b.timestamp?.toMillis() || 0) - (a.timestamp?.toMillis() || 0));
setRecords(data);
setLoading(false);
},
(error) => {
console.error("Firestore error:", error);
setLoading(false);
}
);
return () => unsubscribeData();
}, [fbUser]);
// --- Login Screen ---
if (!userRole) {
return (
Production Management
Select your portal to continue
);
}
return (
{/* Navbar */}
{/* Main Content */}
{loading ? (
) : (
userRole === 'admin' ? :
)}
);
}
// ==========================================
// USER DASHBOARD (DATA ENTRY)
// ==========================================
function UserDashboard({ fbUser }) {
const currentInfo = getProductionInfo(new Date());
const [formData, setFormData] = useState({
loomId: '1',
prodDate: currentInfo.dateString,
shift: currentInfo.shift,
timeSlot: currentInfo.shift === 'Day' ? DAY_SLOTS[0] : NIGHT_SLOTS[0],
meters: ''
});
const [message, setMessage] = useState({ text: '', type: '' });
const availableSlots = formData.shift === 'Day' ? DAY_SLOTS : NIGHT_SLOTS;
useEffect(() => {
setFormData(prev => ({
...prev,
timeSlot: prev.shift === 'Day' ? DAY_SLOTS[0] : NIGHT_SLOTS[0]
}));
}, [formData.shift]);
const handleSubmit = async (e) => {
e.preventDefault();
if (!formData.meters || formData.meters <= 0) {
setMessage({ text: 'Please enter a valid meter quantity.', type: 'error' });
return;
}
try {
const recordsRef = collection(db, 'artifacts', appId, 'public', 'data', 'production_logs');
await addDoc(recordsRef, {
...formData,
meters: Number(formData.meters),
timestamp: serverTimestamp(),
userId: fbUser.uid
});
setMessage({ text: 'Record saved successfully!', type: 'success' });
setFormData(prev => ({ ...prev, meters: '' }));
setTimeout(() => setMessage({ text: '', type: '' }), 3000);
} catch (error) {
console.error("Error adding document: ", error);
setMessage({ text: 'Error saving record. Please try again.', type: 'error' });
}
};
return (
Record Loom Production
Production Day: {currentInfo.dateString}
);
}
// ==========================================
// ADMIN DASHBOARD
// ==========================================
function AdminDashboard({ records }) {
const currentInfo = getProductionInfo(new Date());
const [filters, setFilters] = useState({
startDate: currentInfo.dateString,
endDate: currentInfo.dateString,
loomId: 'all',
shift: 'all'
});
const filteredRecords = useMemo(() => {
return records.filter(record => {
const matchStart = record.prodDate >= filters.startDate;
const matchEnd = record.prodDate <= filters.endDate;
const matchLoom = filters.loomId === 'all' || record.loomId === filters.loomId;
const matchShift = filters.shift === 'all' || record.shift === filters.shift;
return matchStart && matchEnd && matchLoom && matchShift;
});
}, [records, filters]);
const totalMeters = filteredRecords.reduce((sum, r) => sum + (Number(r.meters) || 0), 0);
const activeLooms = new Set(filteredRecords.map(r => r.loomId)).size;
const totalEntries = filteredRecords.length;
const exportToCSV = () => {
if (filteredRecords.length === 0) return;
const headers = ['Date', 'Shift', 'Time Slot', 'Loom Number', 'Meters Produced'];
const csvContent = [
headers.join(','),
...filteredRecords.map(r => `${r.prodDate},${r.shift},${r.timeSlot},Loom ${r.loomId},${r.meters}`)
].join('\n');
const blob = new Blob(['\uFEFF' + csvContent], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.setAttribute("href", url);
link.setAttribute("download", `data_${filters.startDate}_to_${filters.endDate}.csv`);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
return (
{/* Filters Card */}
{/* Stats Cards */}
Total Meters Produced
{totalMeters.toLocaleString()} m
Active Looms
{activeLooms} / 150
Total Records
{totalEntries} entries
{/* Data Table */}
Production Data Log
| Date |
Shift |
Time Slot |
Loom |
Meters |
{filteredRecords.length > 0 ? (
filteredRecords.map((record) => (
| {record.prodDate} |
{record.shift}
|
{record.timeSlot} |
Loom {record.loomId} |
{record.meters} m |
))
) : (
|
No production records found for the selected filters.
|
)}
);
}
```