[{"data":1,"prerenderedAt":302},["ShallowReactive",2],{"portfolio-zenvoy-saas-dashboard":3,"all-portfolio":280},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":7,"category":9,"client":10,"timeline":11,"role":12,"date":13,"url":14,"tags":15,"body":23,"_type":274,"_id":275,"_source":276,"_file":277,"_stem":278,"_extension":279},"\u002Fportfolio\u002Fzenvoy-saas-dashboard","portfolio",false,"","Zenvoy — แพลตฟอร์ม SaaS สำหรับบริหารทีม Remote","SaaS","Zenvoy Technologies","14 สัปดาห์","Full-stack development, Product Design, System Architecture","2024-11-20","https:\u002F\u002Fzenvoy.example.com",[16,17,18,19,20,21,22],"Nuxt 3","TypeScript","Supabase","Tailwind CSS","Chart.js","Stripe","Vercel",{"type":24,"children":25,"toc":266},"root",[26,34,40,45,50,68,73,85,91,112,124,129,234,248,254],{"type":27,"tag":28,"props":29,"children":31},"element","h2",{"id":30},"โจทย์ที่เราได้รับ",[32],{"type":33,"value":30},"text",{"type":27,"tag":35,"props":36,"children":37},"p",{},[38],{"type":33,"value":39},"Zenvoy ต้องการแทนที่ระบบ spreadsheet + Line group ที่ใช้บริหารทีม Remote อยู่เดิม ด้วย web app ที่ HR และผู้จัดการสามารถเห็นภาพรวมทีมได้แบบ real-time รองรับพนักงานทั้งในไทยและต่างประเทศ และเชื่อมกับ payroll provider ที่ใช้อยู่แล้วได้",{"type":27,"tag":35,"props":41,"children":42},{},[43],{"type":33,"value":44},"ความท้าทายคือต้องรองรับ timezone หลายโซน, สกุลเงินหลายสกุล และ permission matrix ที่ซับซ้อน (Employee \u002F Team Lead \u002F HR \u002F Admin \u002F Super Admin)",{"type":27,"tag":28,"props":46,"children":48},{"id":47},"วิธีที่เราแก้ปัญหา",[49],{"type":33,"value":47},{"type":27,"tag":35,"props":51,"children":52},{},[53,55,60,62,66],{"type":33,"value":54},"เราเลือก ",{"type":27,"tag":56,"props":57,"children":58},"strong",{},[59],{"type":33,"value":16},{"type":33,"value":61}," เป็น frontend และ ",{"type":27,"tag":56,"props":63,"children":64},{},[65],{"type":33,"value":18},{"type":33,"value":67}," เป็น backend เพราะ Supabase RLS (Row-Level Security) ทำให้ implement permission matrix ได้ตรงไปตรงมา โดยไม่ต้องเขียน middleware จำนวนมาก",{"type":27,"tag":35,"props":69,"children":70},{},[71],{"type":33,"value":72},"สำหรับ real-time features เช่น attendance tracking และ notification เราใช้ Supabase Realtime Channels ซึ่งลด complexity ได้มากเมื่อเทียบกับ WebSocket server แยก",{"type":27,"tag":35,"props":74,"children":75},{},[76,78,83],{"type":33,"value":77},"Billing ใช้ ",{"type":27,"tag":56,"props":79,"children":80},{},[81],{"type":33,"value":82},"Stripe Billing",{"type":33,"value":84}," แบบ per-seat subscription พร้อม grace period และ dunning management",{"type":27,"tag":28,"props":86,"children":88},{"id":87},"สิ่งที่ทำให้-project-นี้น่าสนใจ",[89],{"type":33,"value":90},"สิ่งที่ทำให้ Project นี้น่าสนใจ",{"type":27,"tag":35,"props":92,"children":93},{},[94,96,101,103,110],{"type":33,"value":95},"ส่วนที่ท้าทายที่สุดคือ ",{"type":27,"tag":56,"props":97,"children":98},{},[99],{"type":33,"value":100},"timezone-aware time tracking",{"type":33,"value":102}," พนักงานในกรุงเทพ, ลอนดอน และโตเกียวต้องเห็นเวลาในโซนของตัวเองเสมอ แต่ database ต้องเก็บ UTC ทั้งหมด เราออกแบบ composable ",{"type":27,"tag":104,"props":105,"children":107},"code",{"className":106},[],[108],{"type":33,"value":109},"useWorkHours",{"type":33,"value":111}," ที่จัดการ conversion อย่างโปร่งใส และเขียน unit test ครอบ edge case ต่างๆ เช่น DST transition และ midnight shift",{"type":27,"tag":35,"props":113,"children":114},{},[115,117,122],{"type":33,"value":116},"นอกจากนี้เราทำ ",{"type":27,"tag":56,"props":118,"children":119},{},[120],{"type":33,"value":121},"export pipeline",{"type":33,"value":123}," ที่สร้าง CSV\u002FExcel ตาม format ที่ payroll provider แต่ละรายต้องการ ซึ่งลด manual work ของ HR ลงได้มากที่สุด",{"type":27,"tag":28,"props":125,"children":127},{"id":126},"ผลลัพธ์",[128],{"type":33,"value":126},{"type":27,"tag":130,"props":131,"children":132},"table",{},[133,157],{"type":27,"tag":134,"props":135,"children":136},"thead",{},[137],{"type":27,"tag":138,"props":139,"children":140},"tr",{},[141,147,152],{"type":27,"tag":142,"props":143,"children":144},"th",{},[145],{"type":33,"value":146},"Metric",{"type":27,"tag":142,"props":148,"children":149},{},[150],{"type":33,"value":151},"ก่อน",{"type":27,"tag":142,"props":153,"children":154},{},[155],{"type":33,"value":156},"หลัง 3 เดือน",{"type":27,"tag":158,"props":159,"children":160},"tbody",{},[161,180,198,216],{"type":27,"tag":138,"props":162,"children":163},{},[164,170,175],{"type":27,"tag":165,"props":166,"children":167},"td",{},[168],{"type":33,"value":169},"เวลา HR ต่อเดือน (ชั่วโมง)",{"type":27,"tag":165,"props":171,"children":172},{},[173],{"type":33,"value":174},"38 ชั่วโมง",{"type":27,"tag":165,"props":176,"children":177},{},[178],{"type":33,"value":179},"15 ชั่วโมง",{"type":27,"tag":138,"props":181,"children":182},{},[183,188,193],{"type":27,"tag":165,"props":184,"children":185},{},[186],{"type":33,"value":187},"ข้อผิดพลาดของ payroll",{"type":27,"tag":165,"props":189,"children":190},{},[191],{"type":33,"value":192},"4–6 ครั้ง\u002Fเดือน",{"type":27,"tag":165,"props":194,"children":195},{},[196],{"type":33,"value":197},"0 ครั้ง",{"type":27,"tag":138,"props":199,"children":200},{},[201,206,211],{"type":27,"tag":165,"props":202,"children":203},{},[204],{"type":33,"value":205},"Onboarding พนักงานใหม่",{"type":27,"tag":165,"props":207,"children":208},{},[209],{"type":33,"value":210},"2 วัน",{"type":27,"tag":165,"props":212,"children":213},{},[214],{"type":33,"value":215},"30 นาที",{"type":27,"tag":138,"props":217,"children":218},{},[219,224,229],{"type":27,"tag":165,"props":220,"children":221},{},[222],{"type":33,"value":223},"NPS Score จากทีม HR",{"type":27,"tag":165,"props":225,"children":226},{},[227],{"type":33,"value":228},"—",{"type":27,"tag":165,"props":230,"children":231},{},[232],{"type":33,"value":233},"78",{"type":27,"tag":235,"props":236,"children":237},"blockquote",{},[238,243],{"type":27,"tag":35,"props":239,"children":240},{},[241],{"type":33,"value":242},"\"ก่อนหน้านี้ปิด payroll แต่ละเดือนใช้เวลาทั้งสัปดาห์ ตอนนี้แค่กดปุ่มเดียว Venoct ช่วยเราได้จริงๆ\"",{"type":27,"tag":35,"props":244,"children":245},{},[246],{"type":33,"value":247},"— คุณนภา ชุมพูทอง, Head of People, Zenvoy Technologies",{"type":27,"tag":28,"props":249,"children":251},{"id":250},"lessons-learned",[252],{"type":33,"value":253},"Lessons Learned",{"type":27,"tag":35,"props":255,"children":256},{},[257,259,264],{"type":33,"value":258},"โปรเจกต์นี้ทำให้เรามั่นใจมากขึ้นว่า ",{"type":27,"tag":56,"props":260,"children":261},{},[262],{"type":33,"value":263},"Supabase RLS เป็น pattern ที่ scale ได้ดีสำหรับ multi-tenant SaaS",{"type":33,"value":265}," เพียงแต่ต้องออกแบบ policy ให้รัดกุมตั้งแต่ต้น เพราะการแก้ไข policy ย้อนหลังบน production table ที่มีข้อมูลเยอะนั้น risky มาก เราเลยพัฒนา checklist สำหรับ RLS design ที่ใช้ในทุกโปรเจกต์ SaaS ตั้งแต่นั้นมา",{"title":7,"searchDepth":267,"depth":267,"links":268},2,[269,270,271,272,273],{"id":30,"depth":267,"text":30},{"id":47,"depth":267,"text":47},{"id":87,"depth":267,"text":90},{"id":126,"depth":267,"text":126},{"id":250,"depth":267,"text":253},"markdown","content:portfolio:zenvoy-saas-dashboard.md","content","portfolio\u002Fzenvoy-saas-dashboard.md","portfolio\u002Fzenvoy-saas-dashboard","md",[281,284,285,288,292,296,299],{"_path":282,"title":283},"\u002Fportfolio\u002Ffreshmarket-platform","Freshmarket Platform",{"_path":4,"title":8,"category":9},{"_path":286,"title":287,"category":9},"\u002Fportfolio\u002Fmedsync-dashboard","MedSync — ระบบจัดการคลินิกและนัดหมายผู้ป่วย",{"_path":289,"title":290,"category":291},"\u002Fportfolio\u002Frunclub-app","RunClub — แอปสำหรับชมรมวิ่งและ community นักวิ่งไทย","Mobile",{"_path":293,"title":294,"category":295},"\u002Fportfolio\u002Fbaan-design-studio","Baan Design Studio — Portfolio Website สำหรับสตูดิโอออกแบบ","Web",{"_path":297,"title":298,"category":9},"\u002Fportfolio\u002Forchard-saas","Orchard — HR & Payroll SaaS สำหรับ SME ไทย",{"_path":300,"title":301,"category":291},"\u002Fportfolio\u002Fbitebuddy-food-app","BiteBuddy — แอปค้นหาร้านอาหารและรีวิวเพื่อชุมชน",1780350561926]