import server from "/people/@server/index.js" import "./PeopleList.js" class People extends Shadow { searchText = "" filterOnline = false listEl = null get people() { return this._people } set people(val) { this._people = val global.currentNetwork.data.members = val } constructor() { super() this._people = (global.currentNetwork.data.members || []).map(p => ({ ...p, _online: this.isOnline(p) })) } isOnline(member) { return global.socket.connectedUsers.includes(member.email) } get filtered() { let list = this.people if (this.filterOnline) list = list.filter(p => p._online) if (this.searchText) { const q = this.searchText.toLowerCase() list = list.filter(p => [p.first_name, p.last_name, p.email] .some(v => v?.toLowerCase().includes(q)) ) } return list } get onlineCount() { return this.people.filter(p => p._online).length } render() { VStack(() => { // ── Header ──────────────────────────────────────────────── VStack(() => { HStack(() => { VStack(() => { HStack(() => { p(`${this.people.length} total`) .margin(0) .fontSize(0.72, em) .color("var(--headertext)") .opacity(0.4) if (this.onlineCount > 0) { HStack(() => { VStack(() => {}) .width(6, px) .height(6, px) .borderRadius(50, pct) .background("#10b981") .flexShrink(0) p(`${this.onlineCount} online`) .margin(0) .fontSize(0.72, em) .color("#10b981") .fontWeight("600") }) .gap(0.3, em) .alignItems("center") } }) .gap(0.65, em) .alignItems("center") .marginTop(0.2, em) }) .gap(0) .flex(1) // Online filter pill if (this.people.length > 0) { HStack(() => { VStack(() => {}) .width(7, px) .height(7, px) .borderRadius(50, pct) .background(this.filterOnline ? "#10b981" : "var(--headertext)") .opacity(this.filterOnline ? 1 : 0.3) .flexShrink(0) p("Online") .margin(0) .fontSize(0.75, em) .fontWeight(this.filterOnline ? "600" : "400") .color("var(--headertext)") .opacity(this.filterOnline ? 1 : 0.45) }) .gap(0.35, em) .alignItems("center") .paddingHorizontal(0.75, em) .paddingVertical(0.35, em) .background(this.filterOnline ? "rgba(16,185,129,0.12)" : "var(--darkaccent)") .border(`1px solid ${this.filterOnline ? "rgba(16,185,129,0.4)" : "var(--divider)"}`) .borderRadius(100, px) .cursor("pointer") .onTap(() => { this.filterOnline = !this.filterOnline; this.rerender() }) } }) .alignItems("flex-start") .marginBottom(0.75, em) // Search bar HStack(() => { p("🔍") .margin(0) .fontSize(0.75, em) .opacity(0.4) .flexShrink(0) input("Search members…", "100%") .border("none") .background("transparent") .color("var(--headertext)") .fontSize(0.88, em) .outline("none") .flex(1) .attr({ value: this.searchText }) .onInput(e => { this.searchText = e.target.value this.listEl._members = this.filtered this.listEl.rerender() }) }) .gap(0.5, em) .paddingHorizontal(0.75, em) .paddingVertical(0.55, em) .background("var(--darkaccent)") .border("1px solid var(--divider)") .borderRadius(0.55, em) .alignItems("center") }) .paddingHorizontal(1, em) .paddingTop(1.25, em) .paddingBottom(0.85, em) .flexShrink(0) // ── Divider ─────────────────────────────────────────────── VStack(() => {}).height(1, px).background("var(--divider)").flexShrink(0) // ── List ────────────────────────────────────────────────── this.listEl = PeopleList(this.filtered, global.appRefreshing || this.people.length === 0) }) .height(100, pct) .width(100, pct) .boxSizing("border-box") .overflow("hidden") .onAppear(async () => { const res = await server.getPeople(global.currentNetwork.id) if (!res.error && res.length > 0 && this.people.length !== res.length) { this.people = res.map(p => ({ ...p, _online: this.isOnline(p) })) this.rerender() } }) } } register(People)