Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/app/pages/job-listings/job-listings.page.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,22 @@ <h1>Job Listings</h1>

<div class="listings-container">
<div *ngFor="let listing of listings" class="listing-card">
<div class="sponsor-header">
<a class="sponsor-header"
[routerLink]="'/app/tabs/sponsors/sponsor-detail/' + getSponsorSlug(listing.sponsor_name)">
<img *ngIf="listing.sponsor_logo_url" [src]="listing.sponsor_logo_url" [alt]="listing.sponsor_name + ' logo'" class="sponsor-logo">
<div class="sponsor-info">
<h2 class="sponsor-name">{{listing.sponsor_name}}</h2>
<span *ngIf="listing.sponsor_level" class="level-badge">{{listing.sponsor_level}}</span>
</div>
</div>
<ion-icon name="chevron-forward" class="sponsor-chevron" aria-hidden="true"></ion-icon>
</a>

<ion-list *ngIf="listing.roles?.length > 0" class="roles-list" lines="none">
<ion-item *ngFor="let role of listing.roles" class="role-item" [button]="!!role.url" (click)="openUrl(role.url)" [detail]="!!role.url">
<ion-icon name="briefcase-outline" slot="start" color="medium" class="role-icon"></ion-icon>
<ion-label class="role-label">
<span class="role-title">{{role.title}}</span>
<span *ngIf="role.location" class="role-location">{{role.location}}</span>
</ion-label>
</ion-item>
</ion-list>
Expand Down
20 changes: 20 additions & 0 deletions src/app/pages/job-listings/job-listings.page.scss
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,19 @@ ion-title {
align-items: center;
gap: 12px;
margin-bottom: 12px;
text-decoration: none;
color: inherit;

&:active {
opacity: 0.6;
}
}

.sponsor-chevron {
margin-left: auto;
font-size: 18px;
color: var(--ion-color-step-400, #a0a0a0);
flex-shrink: 0;
}

.sponsor-logo {
Expand Down Expand Up @@ -133,3 +146,10 @@ ion-title {
overflow: hidden;
overflow-wrap: anywhere;
}

.role-location {
display: block;
margin-top: 2px;
font-size: 0.75rem;
opacity: 0.6;
}
33 changes: 24 additions & 9 deletions src/app/pages/job-listings/job-listings.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,24 @@ export class JobListingsPage implements OnInit {

processListings(raw: any[]): any[] {
return raw.map(listing => {
const structured = Array.isArray(listing.postings) ? listing.postings : [];
const roles = structured.length > 0
? structured
.filter(p => (p.title || '').trim() && (p.url || '').trim())
.map(p => ({
title: (p.title || '').trim(),
location: (p.location || '').trim(),
url: (p.url || '').trim(),
}))
: this.parseRoles(listing.description_html);
return {
...listing,
roles: this.parseRoles(listing.description_html),
roles,
};
});
}

parseRoles(html: string): {title: string, url: string}[] {
parseRoles(html: string): {title: string, location: string, url: string}[] {
if (!html) return [];

// Strip HTML tags to work with plain text
Expand All @@ -47,11 +57,11 @@ export class JobListingsPage implements OnInit {

if (urls.length === 0) {
// No URLs — just return the text as a single entry
return text.length > 0 ? [{title: text, url: ''}] : [];
return text.length > 0 ? [{title: text, location: '', url: ''}] : [];
}

// Try splitting by URLs to find role names
const roles: {title: string, url: string}[] = [];
const roles: {title: string, location: string, url: string}[] = [];
let remaining = text;

for (const url of urls) {
Expand All @@ -63,20 +73,20 @@ export class JobListingsPage implements OnInit {
const lines = before.split(/\n/).filter(l => l.trim());
const title = lines[lines.length - 1].replace(/^[-|–]\s*/, '').trim();
if (title.length > 0 && title.length < 200) {
roles.push({title, url});
roles.push({title, location: '', url});
} else {
roles.push({title: this.shortenUrl(url), url});
roles.push({title: this.shortenUrl(url), location: '', url});
}
} else {
roles.push({title: this.shortenUrl(url), url});
roles.push({title: this.shortenUrl(url), location: '', url});
}
remaining = remaining.substring(idx + url.length).replace(/^\s*[-|–,]\s*/, '').trim();
}
}

// If we couldn't parse anything meaningful, return the raw text as one block
if (roles.length === 0 && text.length > 0) {
return [{title: text, url: ''}];
return [{title: text, location: '', url: ''}];
}

return roles;
Expand Down Expand Up @@ -127,7 +137,8 @@ export class JobListingsPage implements OnInit {
}
const words = this.searchText.toLowerCase().replace(/,|\.|-/g, ' ').split(' ').filter(w => w.trim().length);
this.listings = this.allListings.filter(listing => {
const haystack = `${listing.sponsor_name || ''} ${listing.description_html || ''} ${listing.roles?.map(r => r.title).join(' ') || ''}`.toLowerCase();
const roleText = listing.roles?.map(r => `${r.title} ${r.location || ''}`).join(' ') || '';
const haystack = `${listing.sponsor_name || ''} ${listing.description_html || ''} ${roleText}`.toLowerCase();
return words.every(word => haystack.includes(word));
});
}
Expand All @@ -143,6 +154,10 @@ export class JobListingsPage implements OnInit {
}
}

getSponsorSlug(name: string): string {
return (name || '').toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
}

ngOnInit() {
this.loadListings();
}
Expand Down
Loading