This commit is contained in:
metacryst
2026-04-28 20:05:00 -05:00
commit 0d6c7683ff
123 changed files with 20922 additions and 0 deletions

203
people/desktop/people.js Normal file
View File

@@ -0,0 +1,203 @@
import "./DesktopPeopleToolbar.js"
import "./DesktopPeopleTable.js"
import "./DesktopPeopleDetail.js"
import server from "/people/@server/index.js"
css(`
people- {
font-family: 'Arial';
scrollbar-width: none;
-ms-overflow-style: none;
}
people- select {
appearance: none;
-webkit-appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23888'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 0.65em center;
padding-right: 1.8em !important;
}
people- textarea {
font-family: 'Arial';
}
people- input::placeholder {
color: var(--headertext);
opacity: 0.35;
}
`)
class People extends Shadow {
_people = (global.currentNetwork.data.members || []).map(p => ({ ...p, _online: this.isOnline(p) }))
#lastUpdated;
selectedMemberId = null
searchText = ""
filterRole = ""
filterTier = ""
sortKey = "joined"
sortDir = "asc"
tableEl = null
get people() { return this._people }
set people(val) {
this._people = val
this.#lastUpdated = Date.now()
global.currentNetwork.data.members = val
}
constructor() {
super()
}
isOnline(member) {
return global.socket.connectedUsers.includes(member.email)
}
get filteredSortedPeople() {
let list = this.people;
if (this.searchText) {
const q = this.searchText.toLowerCase();
list = list.filter(p =>
[p.first_name, p.last_name, p.email, p.title, p.county, p.city, p.state]
.some(v => v?.toLowerCase().includes(q))
);
}
if (this.filterRole) {
list = list.filter(p => (p.roles || []).includes(this.filterRole));
}
if (this.filterTier) {
list = list.filter(p => String(p.plan_name) === this.filterTier);
}
// Sort
const key = this.sortKey;
const dir = this.sortDir === "asc" ? 1 : -1;
list = [...list].sort((a, b) => {
let av, bv;
if (key === "name") { av = `${a.first_name} ${a.last_name}`; bv = `${b.first_name} ${b.last_name}`; }
else if (key === "email") { av = a.email || ""; bv = b.email || ""; }
else if (key === "title") { av = a.title || ""; bv = b.title || ""; }
else if (key === "county") { av = a.county || ""; bv = b.county || ""; }
else if (key === "roles") { av = (a.roles || [])[0] || ""; bv = (b.roles || [])[0] || ""; }
else if (key === "tier") { av = a.plan_name || 0; bv = b.plan_name || 0; }
else if (key === "joined") {
av = new Date(a.joined_network || a.created || 0).getTime();
bv = new Date(b.joined_network || b.created || 0).getTime();
}
if (typeof av === "string") return av.localeCompare(bv) * dir;
return (av - bv) * dir;
});
return list;
}
get selectedMember() {
return this.people.find(p => p.id === this.selectedMemberId) || null;
}
handleSort(key) {
if (this.sortKey === key) {
this.sortDir = this.sortDir === "asc" ? "desc" : "asc";
} else {
this.sortKey = key;
this.sortDir = "asc";
}
this.rerender();
}
render() {
const filtered = this.filteredSortedPeople;
const detailOpen = !!this.selectedMember;
VStack(() => {
DesktopPeopleToolbar(
filtered,
this.searchText,
this.filterRole,
this.filterTier,
(text) => {
this.searchText = text
this.tableEl.people = this.filteredSortedPeople
this.tableEl.rerender()
},
(role) => { this.filterRole = role; this.rerender(); },
(tier) => { this.filterTier = tier; this.rerender(); }
)
HStack(() => {
// Table — shrinks when drawer open
VStack(() => {
this.tableEl = DesktopPeopleTable(
filtered,
this.selectedMemberId,
this.sortKey,
this.sortDir,
(id) => {
this.selectedMemberId = this.selectedMemberId === id ? null : id;
this.rerender();
},
(key) => this.handleSort(key)
)
})
.flex(1).height(100, pct).overflow("hidden")
// Slide-out detail drawer
if (detailOpen) {
VStack(() => {
// Drawer header with close button
HStack(() => {
p(`${this.selectedMember.first_name} ${this.selectedMember.last_name}`)
.margin(0).fontSize(0.88, em).fontWeight("600")
.color("var(--headertext)").flex(1).minWidth(0)
.overflow("hidden").whiteSpace("nowrap").textOverflow("ellipsis")
button("✕")
.border("none").background("transparent")
.color("var(--headertext)").opacity(0.4)
.fontSize(0.82, em).cursor("pointer").padding(0.25, em)
.borderRadius(0.3, em)
.onClick((done) => {if(!done) return; this.selectedMemberId = null; this.rerender(); })
})
.paddingHorizontal(1.25, em).paddingVertical(0.78, em)
.borderBottom("1px solid var(--divider)")
.alignItems("center").flexShrink(0)
VStack(() => {
DesktopPeopleDetail(this.selectedMember,
(updatedPerson) => this.updatePeople(updatedPerson)
)
})
.flex(1).overflow("hidden")
})
.width(360, px)
.height(100, pct)
.borderLeft("1px solid var(--divider)")
.flexShrink(0)
.overflow("hidden")
}
})
.flex(1).minHeight(0).width(100, pct).overflow("hidden")
})
.height(100, pct).width(100, pct).overflow("hidden")
.onAppear(async () => {
const res = await server.getPeople(global.currentNetwork.id);
if (!res.error && res.length > 0) {
if((this.people.length !== res.length) || !this.#lastUpdated) {
this.people = res.map(p => ({ ...p, _online: this.isOnline(p) }));
this.rerender();
}
}
})
}
updatePeople(person) {
this.people = this.people.map(p => p.id === person.id ? person : p)
this.rerender()
}
}
register(People)