import React, { useState, useEffect } from 'react'; import { initializeApp } from 'firebase/app'; import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from 'firebase/auth'; import { getFirestore, doc, setDoc, onSnapshot } from 'firebase/firestore'; import { User, GraduationCap, Zap, Code, Rocket, Users, Target, Award, ShieldCheck, Mail, Menu, X, MapPin, Calendar, Quote, Cpu, MessageCircle, Heart, Image as ImageIcon, Palette, Star, CheckCircle, Music, Droplets, BookOpen, ChevronLeft, ChevronRight, Save, Edit3, Eye, FileText, Layout, BarChart3, Info } from 'lucide-react'; // --- Firebase Configuration --- const firebaseConfig = JSON.parse(__firebase_config); const app = initializeApp(firebaseConfig); const auth = getAuth(app); const db = getFirestore(app); const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-tcas-folio'; // --- Initial Data Structure (11 Pages) --- const defaultData = { pages: [ { id: 0, title: 'หน้าปก', sub: 'Cover Page', type: 'cover', name: 'กีรติ คชฤทธิ์', school: 'โรงเรียนนารีนุกูล', faculty: 'คณะศิลปะและการออกแบบ', image: 'https://i.postimg.cc/v81rhMq4/639233864-1638539384263808-1956064543193193054-n.jpg' }, { id: 1, title: 'คำนำ', sub: 'Preface', type: 'text', content: 'แฟ้มสะสมผลงานฉบับนี้จัดทำขึ้นเพื่อนำเสนอตัวตน ทักษะ และความมุ่งมั่นของข้าพเจ้าที่มีต่อการศึกษาในคณะศิลปะและการออกแบบ โดยภายในประกอบด้วยผลงานที่เกิดจากความมุ่งมั่นสร้างสรรค์และการฝึกฝนอย่างต่อเนื่องผ่านประสบการณ์จริง ข้าพเจ้าเชื่อมั่นว่าศิลปะและการออกแบบคือหัวใจสำคัญในการสื่อสารความหมายและคุณค่าของนวัตกรรมสมัยใหม่สู่สายตาชาวโลก ข้าพเจ้าจึงมีความมุ่งมั่นที่จะพัฒนาศักยภาพของตนเองในด้านความคิดสร้างสรรค์อย่างเป็นระบบเพื่อก้าวสู่การเป็นนักออกแบบมืออาชีพที่มีคุณภาพในอนาคต สุดท้ายนี้ ข้าพเจ้าหวังเป็นอย่างยิ่งว่าแฟ้มสะสมผลงานเล่มนี้จะช่วยสะท้อนให้เห็นถึงความพร้อมและแรงผลักดันที่ข้าพเจ้ามีต่อคณะที่ใฝ่ฝันอย่างแท้จริง', image: '' }, { id: 2, title: 'ประวัติส่วนตัว', sub: 'Biography', type: 'profile', birthday: '14 สิงหาคม 2552', location: 'อุบลราชธานี', motto: 'ทำสิ่งที่ถูกต้อง แม้จะเสียเปรียบก็ตาม', image: 'https://i.postimg.cc/v81rhMq4/639233864-1638539384263808-1956064543193193054-n.jpg' }, { id: 3, title: 'ประวัติการศึกษา', sub: 'Education', type: 'education', schoolName: 'โรงเรียนนารีนุกูล', plan: 'ศิลป์-ภาษาอังกฤษ', year: '2024', gpa: '4.00' }, { id: 4, title: 'แดชบอร์ดความสำเร็จ', sub: 'Progress Dashboard', type: 'dashboard', description: 'สรุปสถานะการเตรียมข้อมูลและโครงสร้างพอร์ตโฟลิโอ' }, { id: 5, title: 'กิจกรรม 1', sub: 'Activity 1', type: 'activity', name: 'อาสาสมัครช่วยบริจาคน้ำที่วัดหนองป่าพง', role: 'จิตอาสา / 志愿者 / Volunteer', result: 'ช่วยเหลือชุมชนและสนับสนุนงานบำเพ็ญกุศลทางศาสนา', image: 'https://i.postimg.cc/cCQ283PQ/y-wa.jpg', desc_multilang: "TH: ข้าพเจ้าได้ร่วมกิจกรรมจิตอาสาช่วยบริจาคน้ำดื่มที่วัดหนองป่าพง เพื่อสนับสนุนงานบำเพ็ญกุศลและอำนวยความสะดวกแก่ผู้มาร่วมงาน กิจกรรมนี้ช่วยให้ข้าพเจ้าได้เรียนรู้การทำงานเพื่อส่วนรวมและการเสียสละ\nCN: 我参加了在 Wat Nong Pah Pong 寺院捐赠饮用水的志愿活动,旨在支持宗教活动并为社区服务。这一活动培养了我的公共意识。\nEN: I volunteered to donate drinking water at Wat Nong Pah Pong to support religious ceremonies and serve the community, which fostered my sense of social responsibility." }, { id: 6, title: 'กิจกรรม 2', sub: 'Activity 2', type: 'activity', name: 'ดูงานที่มหาลัยจุฬา', role: 'ผู้เข้าร่วมศึกษาดูงาน / 参观者 / Participant', result: 'เรียนรู้เทคนิคการถ่ายภาพเชิงสร้างสรรค์', image: 'https://i.postimg.cc/2jwx3TKs/588647174-1530741075330940-3401021406201802903-n.jpg', desc_multilang: "TH: เข้าร่วมศึกษาดูงาน ณ จุฬาลงกรณ์มหาวิทยาลัย เพื่อเรียนรู้เทคนิคการถ่ายภาพเบื้องต้นและการจัดวางองค์ประกอบภาพให้เหมาะสม เพื่อสื่อสารเรื่องราวผ่านเลนส์ได้อย่างมีประสิทธิภาพ\nCN: 参观朱拉隆功大学,学习摄影技巧和视觉构图,通过镜头有效传达故事。\nEN: Field trip to Chulalongkorn University to learn photography techniques and composition for effective visual storytelling." }, { id: 7, title: 'กิจกรรม 3', sub: 'Activity 3', type: 'activity', name: 'งานวาดรูปคอมมิชชันที่ตลาด', role: 'ศิลปินอิสระ / 独立艺术家 / Freelance Artist', result: 'ฝึกทักษะการวาดภาพสดและการสื่อสารกับลูกค้า', image: 'https://i.postimg.cc/VLV2TYmN/639340363-26117273227885428-164619013349388829-n.jpg', desc_multilang: "TH: ข้าพเจ้าได้นำทักษะด้านศิลปะมาสร้างรายได้เสริมด้วยการวาดรูปพอร์ตเทรตขายที่ตลาด ซึ่งช่วยฝึกฝนทักษะการวาดรูปสดและการบริหารจัดการธุรกิจขนาดเล็ก\nCN: 在当地市场现场绘画并出售,不仅提升了绘画速度,也锻炼了与客户沟通的能力。\nEN: Utilized artistic skills to sell custom drawings at the market, improving live sketching skills and client communication." }, { id: 8, title: 'เกียรติบัตร 1', sub: 'Certificate 1', type: 'cert', name: 'รางวัลชนะเลิศศิลปหัตถกรรม ครั้งที่ 70', detail: 'ด้านศิลปะและการออกแบบ', image: 'https://i.postimg.cc/y6vr2Vs8/638428575-1461386372168802-5814610247725407885-n.jpg', desc_multilang: "TH: รางวัลชนะเลิศ เหรียญทอง กิจกรรมการแข่งขันศิลปหัตถกรรมนักเรียน ระดับชาติ ครั้งที่ 70\nCN: 第70届学生艺术与手工艺比赛金牌冠军。\nEN: First Prize (Gold Medal), 70th National Student Arts and Crafts Competition." }, { id: 9, title: 'เกียรติบัตร 2', sub: 'Certificate 2', type: 'cert', name: 'รางวัลรองชนะเลิศศิลปหัตถกรรม ครั้งที่ 71', detail: 'การเข้าร่วมโครงการพิเศษ', image: 'https://i.postimg.cc/BbYYFCMq/636507524-1250823250335963-4015151581273909575-n.jpg', desc_multilang: "TH: รางวัลรองชนะเลิศ เหรียญเงิน กิจกรรมการแข่งขันศิลปหัตถกรรมนักเรียน ระดับชาติ ครั้งที่ 71\nCN: 第71届学生艺术与手工艺比赛银牌亚军。\nEN: Runner-up (Silver Medal), 71st National Student Arts and Crafts Competition." }, { id: 10, title: 'การติดต่อ', sub: 'Contact', type: 'closing', email: 'nammm140852@gmail.com', message: 'ขอขอบคุณคณะกรรมการทุกท่านครับ' }, ] }; export default function App() { const [user, setUser] = useState(null); const [data, setData] = useState(null); const [activeTab, setActiveTab] = useState(0); const [isAdmin, setIsAdmin] = useState(false); const [pin, setPin] = useState(""); const [isSaving, setIsSaving] = useState(false); const [loading, setLoading] = useState(true); // --- 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 initialization failed:", err); } }; initAuth(); const unsubscribe = onAuthStateChanged(auth, (u) => setUser(u)); return () => unsubscribe(); }, []); useEffect(() => { if (!user) return; const docRef = doc(db, 'artifacts', appId, 'public', 'data', 'portfolio_collection', 'main_data'); const unsub = onSnapshot(docRef, (docSnap) => { if (docSnap.exists()) { setData(docSnap.data()); } else { setDoc(docRef, defaultData).catch(err => console.error("Initial setDoc failed:", err)); setData(defaultData); } setLoading(false); }, (err) => { console.error("Firestore snapshot error:", err); setLoading(false); }); return () => unsub(); }, [user]); // --- Handlers --- const checkPin = () => { if (pin === "1234") { setIsAdmin(!isAdmin); setPin(""); } else { setPin(""); } }; const handleUpdate = (pageIdx, field, val) => { if (!data) return; const newData = { ...data }; newData.pages[pageIdx][field] = val; setData(newData); }; const handleSave = async () => { if (!user || !data) return; setIsSaving(true); try { const docRef = doc(db, 'artifacts', appId, 'public', 'data', 'portfolio_collection', 'main_data'); await setDoc(docRef, data); setIsAdmin(false); } catch (e) { console.error("Save error:", e); } finally { setIsSaving(false); } }; // --- Helpers for Dashboard --- const calculateCompletion = () => { if (!data) return 0; let totalFields = 0; let filledFields = 0; data.pages.forEach(p => { if (p.type === 'dashboard') return; Object.keys(p).forEach(key => { if (['id', 'title', 'sub', 'type'].includes(key)) return; totalFields++; if (p[key] && p[key].toString().trim() !== "" && p[key].toString().trim() !== "X.XX") { filledFields++; } }); }); return Math.round((filledFields / totalFields) * 100); }; const getPageStatus = (page) => { if (page.type === 'dashboard') return true; let importantFields = Object.keys(page).filter(k => !['id', 'title', 'sub', 'type'].includes(k)); return importantFields.every(k => page[k] && page[k].toString().trim() !== ""); }; // --- Components --- const EditableField = ({ label, value, onUpdate, isArea = false }) => { const displayValue = (typeof value === 'string' || typeof value === 'number') ? value : ""; if (!isAdmin) return {displayValue || "-"}; return (
{isArea ? (