Commit Graph

69 Commits

Author SHA1 Message Date
soroush.asadi 380243b669 Divar geo-coords to facility map + medical gate + RawListing FK/geo migrations
CI/CD / CI · dotnet build (push) Successful in 2m6s
CI/CD / Deploy · hamkadr (push) Successful in 2m3s
2026-06-09 21:38:55 +03:30
soroush.asadi cf5e0011c4 AI ingestion: dynamic role/category creation + tags, hardcoded read-only prompt
CI/CD / CI · dotnet build (push) Successful in 2m19s
CI/CD / Deploy · hamkadr (push) Successful in 2m12s
- Unknown roles from the AI are now resolved-or-CREATED (Persian-normalized dedupe) instead of dropped/fallback; new role gets the AI's category, assigned to the applicant.
- AI output gains category + tags; AI-detected skills/requirements (ICU, MMT, پروانه‌دار…) now fold into the applicant's searchable Tags.
- System prompt is hardcoded in AppSetting.DefaultPrompt and used directly by the auditor; admin sees it read-only (cannot edit/break it).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 19:04:24 +03:30
soroush.asadi 59fb30ac77 AI auditor: surface the real connection error instead of swallowing it
The Test-AI button called AuditAsync, which caught every exception and returned
null, and used EnsureSuccessStatusCode() (discarding the response body). So a
failing AI service only ever produced a generic 'no response' message with no
detail — impossible to diagnose.

- Add IAiAuditor.TestAsync: runs the real call and returns a detailed Persian
  diagnostic — HTTP status + response body on non-2xx, raw body when the shape
  isn't OpenAI-compatible, and network/proxy/timeout specifics on exceptions.
- AuditAsync now logs the actual HTTP status + response body (and proxy state)
  instead of a bare warning, so server logs show why a call failed.
- ExtractContent / ParseVerdict no longer throw on unexpected JSON; they return
  null so the caller can show the raw body.
- Settings 'Test AI' button uses TestAsync; result box renders multi-line and
  switches to alert-error styling when the test fails.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 18:30:12 +03:30
soroush.asadi 753a14286f Mobile hero search: compact magnify button + dropdown under the input
CI/CD / CI · dotnet build (push) Successful in 4m17s
CI/CD / Deploy · hamkadr (push) Successful in 4m30s
- Submit button is now a 44x44 magnify icon inside the search pill on mobile instead of a full-width stacked button (desktop keeps the جستجو text).
- Anchor the typeahead dropdown to the search pill so results appear directly under the input rather than below the popular-search chips; full pill width.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 07:39:23 +03:30
soroush.asadi 62e9bf1353 Nav: replace inline search box with a «🔎 جستجو» link to /Search
CI/CD / CI · dotnet build (push) Successful in 2m39s
CI/CD / Deploy · hamkadr (push) Successful in 2m36s
The hero is now the primary search; the navbar just links to the search
page (cleaner header, less clutter on mobile). Typeahead remains on the
hero (form[data-suggest]).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 23:26:37 +03:30
soroush.asadi 69e2a12a3a Home hero search: polish mobile layout (stacked bordered input + full-width button)
CI/CD / CI · dotnet build (push) Successful in 3m4s
CI/CD / Deploy · hamkadr (push) Successful in 2m26s
On small screens the pill now stacks cleanly: a bordered, padded input above
a full-width جستجو button; icon hidden; chips centered. Shorter placeholder so
it never overflows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 23:14:25 +03:30
soroush.asadi bcf90f2437 Home hero: replace filter dropdowns with a search-engine box (+ live typeahead)
CI/CD / CI · dotnet build (push) Successful in 2m32s
CI/CD / Deploy · hamkadr (push) Successful in 2m0s
The hero is now a single big search box → /Search (the rich, ranked,
highlighted search across shifts/jobs/applicants), with popular-search
chips. Typeahead is generalized to any form[data-suggest], so the hero box
shows the same instant highlighted dropdown as the header pill.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 22:34:31 +03:30
soroush.asadi 1e96526bd9 Review/publish: multi-select roles → one listing per role
An ad can cover several roles (e.g. «پرستار سالمند و کودک و همراه بیمار»).
The role dropdown is now a checkbox multi-select; on publish we fan out and
create one Shift/Job/Talent per selected role (mirrors the auto-ingest
fan-out). Jobs get a per-role title when multiple are chosen; talent
listings each get their own contact rows; all created items notify matches.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 22:03:09 +03:30
soroush.asadi 5e5d7f80ef Admin queue: show fetched time (HH:mm) alongside date on review + ingested rows
CI/CD / CI · dotnet build (push) Successful in 2m20s
CI/CD / Deploy · hamkadr (push) Successful in 2m3s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 21:55:03 +03:30
soroush.asadi 8b0b21f24d Search: Elasticsearch-style highlighted match snippets (results + typeahead)
CI/CD / CI · dotnet build (push) Successful in 6m9s
CI/CD / Deploy · hamkadr (push) Has been cancelled
- SearchHighlight.Snippet: extracts a ±70-char window around the first
  matching term and marks it (with ellipses) — the ES "highlight" fragment.
- Result cards (shift/job/talent) now show that snippet from the matched
  description/tags when a query is present, so you SEE where the term hit
  (e.g. «…دارای مدرک <mark>mmt</mark>…») instead of just the role.
- Typeahead suggestions gain a highlighted "sub" line (talent→tags,
  shift→city·specialty, job→facility·city) so matches show in the dropdown too.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 21:43:50 +03:30
soroush.asadi eb7d0f6559 Fix: search suggestions dropdown was clipped by the pill's overflow:hidden
CI/CD / CI · dotnet build (push) Failing after 14s
CI/CD / Deploy · hamkadr (push) Has been skipped
Moved overflow:hidden onto an inner .nav-search-pill so the rounded corners
still clip the input/button, but the absolutely-positioned suggestions box
(a child of the non-clipped .nav-search) is no longer hidden. Dropdown given
a readable min-width.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 17:42:07 +03:30
soroush.asadi 61afc957aa Search: fix header UI + instant typeahead (5 highlighted matches) + ranking
CI/CD / CI · dotnet build (push) Successful in 1m38s
CI/CD / Deploy · hamkadr (push) Successful in 2m6s
- Header search restyled as one clean RTL pill (input + button flush).
- Google-style autocomplete: typing ≥2 chars fetches /search/suggest and
  shows up to 5 live matches (round-robin across shifts/jobs/applicants)
  with the query highlighted, plus a «همه نتایج» link. Debounced, closes on
  outside-click/Escape.
- Search results page now RANKS by relevance (term hits in role/title/
  facility/city/tags weighted ×3, description ×1) instead of date-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 11:58:30 +03:30
soroush.asadi 9db4deafbc Site-wide rich search with keyword highlighting + header search box
CI/CD / CI · dotnet build (push) Successful in 1m53s
CI/CD / Deploy · hamkadr (push) Successful in 2m47s
- /Search: searches shifts, hiring openings, and applicants together via
  Postgres ILIKE (every term must match across role/city/facility/title/
  description/tags/person). Results grouped per type.
- Keyword highlighting (<mark>) extended to shift & job cards (was talent-only),
  so matches stand out everywhere.
- Persistent header search box (.nav-search) → /Search; big hero box on the
  page itself.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 11:40:26 +03:30
soroush.asadi 234bcd1f88 Polished animated contact-reveal box (shift/job/talent details)
CI/CD / CI · dotnet build (push) Successful in 3m46s
CI/CD / Deploy · hamkadr (push) Has been cancelled
Replaced the plain "interest recorded" alert with a styled .contact-reveal
card that fades/slides in and lists each channel as its own row (icon +
label + value + action button). Shift/job show facility phone + Bale;
talent shows all its ContactMethods in the same table style.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 11:33:41 +03:30
soroush.asadi 6b657c7795 Applicants: auto-tags + deep search w/ highlight; never delete (archive instead)
CI/CD / CI · dotnet build (push) Successful in 2m1s
CI/CD / Deploy · hamkadr (push) Successful in 2m36s
- Tags: parser extracts cert/skill keywords (mmt, ICU/CCU, دیالیز, اتاق عمل,
  اورژانس, مسئول فنی, پروانه‌دار…) + role + city into TalentListing.Tags
  (+ migration); shown as chips on cards.
- Deep search on /Talent: «جستجوی عمیق» box does Postgres ILIKE across
  tags, description, person, area, role, city (every term must match);
  matches are highlighted with <mark> via SearchHighlight.
- Never delete: ShiftStatus.Archived + the admin «بایگانی گروهی» action now
  ARCHIVES aggregated posts (hidden from site, kept in DB) and leaves the
  raw crawl rows intact — a permanent archive for future analytics.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 11:25:32 +03:30
soroush.asadi e4dc5180ad Applicants: 1→N contact methods with types (phone/email/Instagram/Telegram/Bale/site)
CI/CD / CI · dotnet build (push) Successful in 1m32s
CI/CD / Deploy · hamkadr (push) Successful in 1m31s
- ContactMethod entity (Type + Value + SortOrder) 1→N on TalentListing (+ migration).
- Parser extracts ALL contacts: multiple phones + landlines, email, and
  socials (Instagram/Telegram/Bale/WhatsApp/website) via URLs and Persian
  keyword cues; primary Phone kept for cards.
- ContactInfo helper: per-type label/icon/clickable href (tel:/mailto:/t.me/…).
- Ingestion attaches contacts to each (fanned-out) talent listing; manual
  Review re-parses to attach them + the admin-typed phone.
- Talent details renders the full contact list as buttons; falls back to the
  single phone, then the Divar source link.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 11:10:19 +03:30
soroush.asadi fb02c81830 Social auto-posting (phase 1): daily applicant digest to Telegram/Bale + Instagram caption
CI/CD / CI · dotnet build (push) Successful in 1m51s
CI/CD / Deploy · hamkadr (push) Successful in 2m51s
Adds a «شبکه‌های اجتماعی» admin section + scheduler that publishes a daily
«کادر آماده‌به‌کار امروز» digest:

- AppSetting: social toggles, posts-per-day, editable header/footer,
  per-channel bot token + chat id (Telegram, Bale), Instagram enable +
  extra hashtags, proxy toggle, last-posted timestamp (+ migration).
- SocialPostService: builds today's talent digest as text, posts to
  Telegram and Bale via their bot sendMessage APIs (proxy-aware), and
  produces an Instagram caption + auto hashtags (role/city based).
- SocialPostWorker: posts N times/day, evenly spaced, self-paced; reads
  settings live so it's togglable without redeploy.
- /Admin/Social: credentials + header/footer + posts/day, live preview of
  today's message, «ارسال اکنون» button, and an Instagram caption pack
  with copy button (semi-automatic — you post the image manually).
- Nav link added.

Telegram/Bale post as TEXT (per request). The Vazirmatn image card for
Instagram is phase 2 (needs SkiaSharp+HarfBuzz + a TTF font).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 09:20:49 +03:30
soroush.asadi 2bb8771ade Normalize ریال→تومان pricing; stop exposing crawl source (medjobs/telegram)
CI/CD / CI · dotnet build (push) Successful in 29s
CI/CD / Deploy · hamkadr (push) Successful in 42s
- Parser now reads the currency: ریال amounts (incl. «میلیون ریال» and
  numbers with no تومان unit but ≥200M) are converted to تومان (÷10), so
  «۴۰۰٬۰۰۰٬۰۰۰ ریال» shows as ۴۰٬۰۰۰٬۰۰۰ تومان instead of 400M.
- Aggregated facility fallback name no longer embeds the source
  («مرکز درمانی (از مدجابز)» → «مرکز درمانی (نامشخص)»).
- Talent details only ever names Divar as a fallback source (when the
  number couldn't be extracted); medjobs/telegram are never shown publicly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 09:05:34 +03:30
soroush.asadi 490821a637 Talent lifecycle (21-day expiry) + noindex expired job/shift details
CI/CD / CI · dotnet build (push) Successful in 2m24s
CI/CD / Deploy · hamkadr (push) Successful in 2m47s
- Talent «آماده به کار» now has its own freshness window (21 days, vs 30
  for jobs) since availability goes stale fast; archiver, browse, and home
  use TalentCutoffUtc.
- Expired/filled job openings and past/filled shifts now emit
  robots noindex so Google drops dead listings instead of keeping
  soft-404 pages. (Talent details were already noindex.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 08:59:54 +03:30
soroush.asadi f9d7c48d88 Admin settings: give each ingestion source its own card
CI/CD / CI · dotnet build (push) Successful in 2m17s
CI/CD / Deploy · hamkadr (push) Successful in 1m57s
The sources panel (Telegram/Bale/Divar/Medjobs/Websites/Proxy) ran
together as one flat list. Each is now wrapped in a bordered .source-box
with an icon + hint, so it's clear where one source's settings end and the
next begins.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 08:49:27 +03:30
soroush.asadi b092a5cfe5 Admin: bulk-delete published ingested posts; talent: point to source when no phone
CI/CD / CI · dotnet build (push) Successful in 1m52s
CI/CD / Deploy · hamkadr (push) Successful in 2m41s
- /Admin/Ingested: "حذف گروهی همه‌ی منتشرشده‌ها" button removes, in one
  transaction, every aggregated Shift/Job/Talent published from ingestion
  plus the approved (Normalized) raw items that produced them. Confirms
  first and reports counts. Raw rows deleted before the posts (they hold
  the FKs); DB cascade clears applications/interest events.
- Talent details: when the contact number couldn't be extracted (e.g.
  Divar's login-gated reveal), show a prominent "مشاهده شماره در دیوار/مدجابز ↗"
  link to the original ad instead of the call button.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 08:36:12 +03:30
soroush.asadi 4e5df73cf7 Add «آماده به کار» (talent) listing type — workers offering themselves
CI/CD / CI · dotnet build (push) Successful in 1m41s
CI/CD / Deploy · hamkadr (push) Has been cancelled
Adds a third listing kind alongside Shift/Job for healthcare staff who
advertise their own availability (very common in Iranian medical
channels, e.g. "دندانپزشک آماده همکاری… ۵۰٪ تسویه"). These have no
facility; the contact phone is the key field.

- Model: TalentListing (role, person name, years, licensed, city/district,
  area note, availability, gender, comp, phone) + ListingKind.Talent +
  RawListing.LinkedTalentId + DbSet/relations/indexes + EF migration.
- Parser: detect آماده‌به‌کار/جویای کار → Kind=Talent; extract person name,
  years of experience, licensed flag, area («منطقه ۱»), phone. Facility
  name extraction now skipped for talent.
- Validator: talent path scores role + phone + medical (no facility/pay
  required).
- Ingestion auto-publish: creates a TalentListing for talent kind.
- Review (manual publish): Talent option + talent fields; publishes a
  TalentListing without a facility. Shift/Job facility now falls back to a
  shared «نامشخص / ثبت نشده» record when the ad names none — publishing
  never fails on a missing facility.
- Browse /Talent (indexable, filters: city/district/role/gender),
  details /Talent/Details (noindex — personal contact, tel: call button),
  _TalentCard, badge-talent, nav link, home section.
- Sitemap includes /Talent; robots disallows /Talent/Details. Archiver
  expires stale talent listings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 08:01:12 +03:30
soroush.asadi bdcca5e548 Redesign header menu: separate account dropdown from dashboard nav
CI/CD / CI · dotnet build (push) Successful in 1m7s
CI/CD / Deploy · hamkadr (push) Successful in 1m57s
The profile dropdown was doing three jobs at once (account actions, the
job-seeker panel menu, and the admin panel menu) and a stray inline @if
for the notification badge leaked into the markup as literal text.

- Profile dropdown is now account-only: identity card (avatar + name +
  phone), one role-aware dashboard entry, edit profile, logout. This
  removes the leaked @if and de-clutters the menu.
- Dashboard menu is centralized in _PanelNav and auto-rendered by the
  layout on every logged-in panel page (/Admin, /Me, /Employer,
  /Preferences) instead of being duplicated in the dropdown and pages.
- Drop the now-duplicate manual <partial name="_PanelNav" /> from
  Overview, Ingested, Me/Index, Employer/Index.
- CSS: identity-card (.pd-id) styles + mobile tweaks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 07:33:22 +03:30
soroush.asadi e6a796ab27 Match crawled listings to existing facilities (fuzzy) before creating new
CI/CD / CI · dotnet build (push) Successful in 1m28s
CI/CD / Deploy · hamkadr (push) Successful in 2m24s
When publishing a scraped listing we now look for a facility we already
have that is exactly or closely the same, and only create a new one when
there is no match — avoiding duplicates like «بیمارستان میلاد» vs «میلاد».

- ListingParser: extract a facility name (keyword + distinctive words) from
  the post and surface it in the parser notes.
- FacilityMatcher: Persian-aware normalization (ي/ك, ZWNJ, punctuation),
  type-word stripping for a "core" name, contains + Levenshtein similarity,
  and FindBest (same-city exact → any-city exact → same-city fuzzy → fuzzy).
- Review (manual publish): auto-select a matching facility or prefill the
  new-facility name; resolve-or-create uses fuzzy match; dropdown preselects.
- IngestionService (auto-publish): reuse FacilityMatcher against a run-wide
  facility list (grows as new ones are created) instead of exact-name only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 07:14:48 +03:30
soroush.asadi a2fc70ae57 Fix FK violation when publishing a crawled listing without a facility
CI/CD / CI · dotnet build (push) Successful in 1m31s
CI/CD / Deploy · hamkadr (push) Successful in 1m44s
OnPostPublishAsync inserted a Shift/Job with FacilityId=0 when no
facility was selected (e.g. the dropdown is empty because no facilities
exist yet), throwing FK_Shifts_Facilities_FacilityId and surfacing the
production error page.

- Resolve-or-create the facility before insert: use the picked one, else
  create an unverified Facility from a typed name (reusing same-named).
- Guard the role too; on missing facility/role redirect back with a
  Persian error message instead of 500.
- Review form: add "new facility name" input + "— none —" option +
  error alert; add .alert-error style.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 07:09:18 +03:30
soroush.asadi da6e86fa7f [Ingest] Full results page (all statuses) + inline quick-reject in queue
CI/CD / CI · dotnet build (push) Successful in 2m13s
CI/CD / Deploy · hamkadr (push) Has been cancelled
New /Admin/Ingested page lists every crawled item with its outcome, filterable by status (همه/در صف/پرچم‌خورده/منتشرشده/ردشده) with per-status counts and a link to the published shift or the review page. Linked from the run-history header and the admin panel nav. Plus an inline ✕رد (quick-discard) button on each queue/flagged row so admins can audit without opening the review page; full accept/reject stays on /Admin/Review.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 06:41:17 +03:30
soroush.asadi 3d128ea051 [Brand] Branded favicon + icon links in <head>
CI/CD / CI · dotnet build (push) Successful in 1m43s
CI/CD / Deploy · hamkadr (push) Failing after 1m35s
Replace the default ASP.NET favicon.ico with one generated from the همکادر brand icon (multi-size 16/32/48/64), add favicon-32.png, and wire <link rel=icon> (ico + png 32/192) in the layout head so browsers and Google show the brand mark.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 06:28:01 +03:30
soroush.asadi 487c7ca82f [Ingest] Persistent crawl run-log + per-source breakdown on admin queue
CI/CD / CI · dotnet build (push) Has been cancelled
CI/CD / Deploy · hamkadr (push) Has been cancelled
Each ingestion run now records an IngestionRun row (found/queued/published/flagged/spam/duplicates + a per-source detail string). Admin → صف آگهی‌ها shows a «تاریخچه جمع‌آوری» table of the last 15 runs (hover a row for the per-source breakdown), so admins can see how much each source found vs added over time. IngestionSummary gains TotalFetched. Migration: IngestionRuns table.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 06:23:58 +03:30
soroush.asadi 524c66e25e [Admin] VPN/proxy + AI test buttons; fix AI JSON parse crash on null fields
CI/CD / CI · dotnet build (push) Successful in 2m41s
CI/CD / Deploy · hamkadr (push) Failing after 2m56s
Add «تست اتصال VPN/پروکسی» (reaches a filtered site through the proxy and reports connected/latency) and «تست هوش مصنوعی» (sends a sample post through the configured model and shows the verdict + extracted fields) to admin Settings. Fix OpenAiCompatibleAuditor.ParseVerdict: TryGetInt32/64 threw on null/string JSON values (the model commonly returns payAmount/sharePercent as null), which silently failed every audit — now guarded on ValueKind==Number. Verified the real OpenAI key extracts perfectly (approve / role=پرستار / city=تهران / shift=night).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 23:23:02 +03:30
soroush.asadi 0c49b89891 [AI] Route AI calls through the Xray/V2Ray proxy (reach OpenAI from Iran)
CI/CD / CI · dotnet build (push) Successful in 1m46s
CI/CD / Deploy · hamkadr (push) Failing after 1m58s
Add AiUseProxy setting + a toggle in the AI settings section. ScrapeHttpClients.ForAi(settings) returns a proxied HttpClient (reusing IngestProxyUrl, 100s timeout) when AiUseProxy is on, otherwise direct; AI-cache keys are protected from the scrape-client cleanup. OpenAiCompatibleAuditor now uses it, so the AI auditor (e.g. api.openai.com) is reachable through the same Xray sidecar that serves Telegram. Migration adds the column.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 22:55:07 +03:30
soroush.asadi 33c13ec524 [Dashboard] Panel sub-nav menu + clearer profile button (name, not phone digit)
CI/CD / CI · dotnet build (push) Successful in 2m11s
CI/CD / Deploy · hamkadr (push) Failing after 2m37s
Logged-in panels (admin/employer/job-seeker) now show a sticky role-based dashboard menu (_PanelNav) on Employer/Index, Me/Index and Admin/Overview, with the active section highlighted — so users have an obvious menu and dashboard, not just a hidden avatar. Profile button: avatar fallback shows a 👤 glyph instead of the phone's first digit (the confusing '0'), and the desktop button now shows the user's name (or «حساب من») so it reads as a profile menu.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 22:12:51 +03:30
soroush.asadi 69e4f305e9 [Nav] Add ثبت آگهی CTA, streamline menu, active-link highlight, role dashboards
CI/CD / CI · dotnet build (push) Successful in 3m26s
CI/CD / Deploy · hamkadr (push) Failing after 2m41s
Header gets a prominent accent +ثبت آگهی CTA → /Employer/Index (auth redirect handles login → register/post). Main nav trimmed to the 5 core public links (خانه/شیفت‌ها/استخدام/مراکز/تقویم); دریافت اپ + راهنما live in the footer and علاقه‌مندی‌ها in the profile menu, so the bar is far less crowded. Added active-page highlight (accent underline on desktop, soft background on mobile). Login now sends admins to /Admin/Overview (dashboard) instead of the ingestion queue; employers→/Employer/Index, job-seekers→/Me already in place.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 21:38:08 +03:30
soroush.asadi 6af6a026a1 [SEO] JobPosting structured data, canonical/OG meta, noindex private pages, fuller sitemap
CI/CD / CI · dotnet build (push) Successful in 59s
CI/CD / Deploy · hamkadr (push) Successful in 2m27s
Strategy = Google-for-Jobs + clean indexing. Add schema.org JobPosting JSON-LD to shift & job detail pages (title, description, datePosted, validThrough, employmentType, hiringOrganization, jobLocation, baseSalary) plus Organization + WebSite JSON-LD on the home page (SeoJsonLd helper; System.Text.Json => valid, script-safe). Layout emits per-page canonical, Open Graph + Twitter cards, and applies robots noindex,nofollow to all private/applicant areas (/Admin,/Me,/Employer,/Account,/Preferences) so applicant data is never indexed. robots.txt now disallows those + /resume,/avatar,/report,/push,/notifications and points at the sitemap; sitemap.xml adds facility pages + content pages (Download/Help/Privacy/Rules/Terms).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 08:16:30 +03:30
soroush.asadi aa61efd46f [Applicant+Admin] Withdraw application, delete account, admin analytics dashboard
CI/CD / CI · dotnet build (push) Successful in 47s
CI/CD / Deploy · hamkadr (push) Successful in 1m28s
Applicant: 'انصراف از درخواست' on /Me removes the Apply event for that shift/job. Account: 'حذف حساب من' on /Me/Profile permanently deletes the user + cascades (profile, alerts, reviews, applications), detaches anonymous visitor history, and signs out (per privacy policy). Admin: /Admin/Analytics dashboard — totals (users, facilities/verified, open shifts/jobs, applications, reviews), 7-day deltas, and a 14-day applications bar chart; linked from Overview alongside the new نظرات moderation page.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 07:52:49 +03:30
soroush.asadi d87afb577c [Facilities] Public facility pages + ratings & reviews
New /Facilities/Details public page: verified badge, info, Neshan map + directions, the facility's open shifts & jobs, and a complaint form; facility cards on /Facilities link to it. Ratings & reviews: Review model (1-5 stars + comment, one per user/facility, unique index, migration); logged-in users rate/review on the facility page; average + count shown in the header and the review list; admins moderate (hide/delete) at /Admin/Reviews.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 07:44:25 +03:30
soroush.asadi c46e628f6a [Profile] Show applicant avatar + resume to employers; profile-completeness nudge
CI/CD / CI · dotnet build (push) Successful in 1m33s
CI/CD / Deploy · hamkadr (push) Successful in 2m7s
Employer Listings: each applicant row now shows their avatar (or initials) and a «مشاهده رزومه» link when they uploaded one (served via /resume/{id}, already access-controlled to the receiving employer). Applicant projection avoids loading avatar/resume blobs. Me panel: a nudge banner prompts users to complete their profile (name/photo/resume) when any is missing, linking to /Me/Profile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 07:14:03 +03:30
soroush.asadi e633463906 [Profile] Editable profile (avatar + resume) + role-based profile dropdown menu
CI/CD / CI · dotnet build (push) Successful in 44s
CI/CD / Deploy · hamkadr (push) Successful in 57s
Every user gets a full editable profile at /Me/Profile: name, role, city, specialty/title, license, years, bio + avatar image upload + resume upload (PDF/image). Avatar/resume stored in-DB on User (migration, 5 nullable columns). Endpoints: /avatar/{id} (public) and /resume/{id} (owner, admin, or an employer who received that user's application). Nav: replaced the scattered action links with an avatar button + dropdown listing all of the user's pages by role (profile, کارجو panel, alerts, preferences, notifications; employer panel; admin panel + settings; logout) — shows the avatar image or initials; collapses into the burger menu on mobile; closes on outside-click.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 21:49:40 +03:30
soroush.asadi 167d263560 [Applications] Applicant pipeline: employer accept/reject + status to applicant
CI/CD / CI · dotnet build (push) Successful in 43s
CI/CD / Deploy · hamkadr (push) Successful in 43s
InterestEvent gains a Status (ApplicationStatus: Interested→Accepted/Rejected; migration, default Interested). Employer/Listings shows each applicant's status with پذیرفتن/رد buttons (ownership-checked handlers update the status and notify the applicant via bell/SSE/push linking to the listing). The کارجو panel (/Me) now shows a status badge (در انتظار بررسی / پذیرفته شد / رد شد) on each applied shift/job. Reusable _ApplicantRow partial for the employer list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 21:27:53 +03:30
soroush.asadi 2170ba250c [UI] Mobile sticky action bar on shift/job details (native-app feel)
CI/CD / CI · dotnet build (push) Successful in 46s
CI/CD / Deploy · hamkadr (push) Successful in 1m6s
On mobile the action panel (CTA + map) stacked at the very bottom, so users had to scroll the whole page to act. Add a fixed bottom action bar (<=860px) with the primary «اعلام تمایل» button + a quick save, always reachable like a native app; when contact is revealed it becomes a «تماس با مرکز» tel: button. The in-aside primary CTA is hidden on mobile (.aside-apply) to avoid duplication, and pages get bottom padding (.has-action-bar) so the bar never covers content. Desktop layout unchanged (bar hidden, sidebar CTA shown).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 19:12:34 +03:30
soroush.asadi 86809190e7 [Map] Render real Neshan map on shift/job detail pages
CI/CD / CI · dotnet build (push) Successful in 1m6s
CI/CD / Deploy · hamkadr (push) Successful in 1m12s
The detail pages showed a 'map coming later' placeholder. Add a read-only Neshan web map (Leaflet SDK) showing the facility marker when NeshanMapKey is set, plus a 'مسیریابی در نشان' directions link; falls back to coordinates when no key. New _NeshanMap shared partial loads the SDK and inits #facmap. Shift/Job Details models now expose MapKey via SettingsService. Coordinates are emitted with InvariantCulture so the decimal point/digits don't break JS. The facility registration picker already used Neshan; this reuses the same key.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 18:57:49 +03:30
soroush.asadi b1e474ba33 [Ingest] Per-source proxy toggle instead of one global switch
CI/CD / CI · dotnet build (push) Successful in 56s
CI/CD / Deploy · hamkadr (push) Successful in 1m6s
Each ingestion source now decides independently whether to route through the proxy: added TelegramUseProxy/BaleUseProxy/DivarUseProxy/MedjobsUseProxy/WebsitesUseProxy flags (migration). ScrapeHttpClients.For(s, useProxy) takes the source's own flag; a source is proxied only when its flag is on AND a proxy URL is set. Settings 'sources' tab: removed the global enable checkbox, kept the proxy address field, and added an «از پروکسی استفاده شود» checkbox under each source. Old IngestProxyEnabled column kept for compatibility but no longer gates routing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 18:46:48 +03:30
soroush.asadi cde6b68a39 [Admin] Redesign Settings as sidebar tabs + style password/toggle fields
CI/CD / CI · dotnet build (push) Successful in 35s
CI/CD / Deploy · hamkadr (push) Successful in 59s
Split the long settings page into 7 sidebar tabs (publish+AI, sources, channels, SMS, push, map, demo) with a single form so one Save persists everything; seed/clear/test are submit buttons targeting their handlers via asp-page-handler. Boolean settings now render as clean .toggle-row cards. CSS fix: the form input rule omitted input[type=password] (and url/email/search), so API-key/VAPID/token fields were unstyled — added them, plus accent-color + sizing for checkboxes/radios. Active tab persists across handler posts via sessionStorage; layout collapses to a horizontal tab strip on mobile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 18:25:06 +03:30
soroush.asadi 213faadf55 [Alerts] Customizable job alerts + Help capabilities showcase
CI/CD / CI · dotnet build (push) Successful in 1m8s
CI/CD / Deploy · hamkadr (push) Successful in 1m7s
Job alerts (هشدار شغلی): users save what they want — scope (shift/job/both), role, city, shift type, employment type, minimum pay — and get notified when an employer posts a match. New JobAlert model + AlertScope enum + DbContext (user-cascade, role set-null, IsActive index) + migration. /Me/Alerts page to create/pause/delete alerts; entry point added to the کارجو panel. NotificationService.NotifyNewShift/Job now unions preference matches with active-alert matches (deduped) so alert owners are notified on publish. Help page gains an 'امکانات همکادر' capability showcase grid (with a 'ساخت هشدار شغلی' CTA) so the page demonstrates what the app does.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 18:17:56 +03:30
soroush.asadi cea27c8684 [Ingest] Route scraping through an optional V2Ray/Xray proxy (Telegram in Iran)
CI/CD / CI · dotnet build (push) Successful in 53s
CI/CD / Deploy · hamkadr (push) Successful in 1m12s
Telegram and some sources are filtered in Iran. .NET cannot speak vmess/vless/trojan, so add an Xray sidecar (compose service 'xray', behind the 'proxy' profile) that converts the admin's config into a local SOCKS5 proxy (xray:10808). New ScrapeHttpClients provider builds a proxied or direct HttpClient (WebProxy supports socks5/socks4/http) cached per proxy URL; all five ingestion sources (Telegram/Bale/Divar/Medjobs/Websites) now use it. Admin settings gain IngestProxyEnabled + IngestProxyUrl (migration; UI under sources). Added deploy/xray/config.json template + README with vmess/vless/trojan examples.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 17:53:17 +03:30
soroush.asadi 698565c460 [Help] Add help/learning page + interactive guided app tour
CI/CD / CI · dotnet build (push) Successful in 46s
CI/CD / Deploy · hamkadr (push) Successful in 1m10s
New /Help page: quick-start, separate guides for کادر درمان (search/near-me/preferences/اعلام تمایل/saved) and مراکز درمانی (register/post/verify-with-docs/applicants), notifications+install, report/complaint, and an FAQ accordion. Self-hosted tour.js (no CDN, RTL): spotlights elements via data-tour hooks in the nav, auto-runs once for new visitors on the home page (localStorage flag), re-runnable from the Help page button or ?tour=1; skips steps whose target is hidden so it works on mobile/other pages. Help linked from nav + footer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 17:39:03 +03:30
soroush.asadi c7e4bf059e [Legal] Add Privacy, Rules, and Terms of Use pages (Persian/RTL) + footer links
CI/CD / CI · dotnet build (push) Successful in 1m0s
CI/CD / Deploy · hamkadr (push) Successful in 1m11s
Three policy pages tailored to همکادر: /Privacy (data collected, use, sharing, no Google services, cookies, security), /Rules (accurate info, allowed listings, conduct, prohibited content, verification badge meaning, reports), /Terms (intermediary nature/no employment guarantee, account terms, scraped-listing disclaimer, liability, IP, governing law = Iran). Linked from the footer; .legal long-form reading style added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:48:10 +03:30
soroush.asadi 1f34fd126f [Verify+Complaints] Facility document review + facility complaints; card location line
CI/CD / CI · dotnet build (push) Successful in 1m27s
CI/CD / Deploy · hamkadr (push) Successful in 1m13s
Card: move location to its own line above the date in the shift card (job card already did). Verification workflow: employers upload documents (license/permit) on a new Employer/Verify page; uploading marks the facility Pending. Admins see pending facilities with their documents on Admin/Facilities, can download each doc, and approve (تأیید شد) or reject with a reason. Documents stored as bytea in the DB (survives deploys via the existing volume); served only to the owner or an admin via /facility-doc/{id}. Facility model gains Verification status enum + note + requested-at; IsVerified kept in sync. Complaints: registered users/visitors can file a شکایت about a facility from shift/job detail pages (targets ReportTargetType.Facility, surfaces in Admin/Reports as مرکز). Migration backfills existing verified facilities to Verified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:26:15 +03:30
soroush.asadi 8fad9c1bb6 [Admin] Notification channel toggles (web/SMS/push active-deactive)
CI/CD / CI · dotnet build (push) Successful in 50s
CI/CD / Deploy · hamkadr (push) Successful in 1m1s
Add a 'notification channels' card at the top of admin Settings with three master on/off checkboxes: web/in-app (new WebNotificationsEnabled, default true), SMS (existing SmsEnabled), and Web Push (existing PushEnabled). Removed the duplicate enable checkboxes from the SMS and Push sections so each binds once. NotificationService now gates the in-app + live SSE channel on WebNotificationsEnabled; push self-gates on PushEnabled. Migration defaults the new column to true so existing installs keep web notifications on.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:56:40 +03:30
soroush.asadi 716433ce20 [Notify] Add live in-app notifications over SSE (Iran-friendly)
CI/CD / CI · dotnet build (push) Has been cancelled
CI/CD / Deploy · hamkadr (push) Has been cancelled
Web Push is delivered by the browser vendor's push service (Chrome to Google FCM), which is filtered in Iran, so background push is unreliable. Add a Server-Sent Events channel over our own origin that always reaches users while the tab/PWA is open: NotificationHub (in-memory pub/sub), a /notifications/stream SSE endpoint (auth-gated, keep-alive pings, nginx no-buffer), and NotificationService now publishes each saved notification to the hub. Client updates the bell badge instantly, shows a toast, and fires a local OS notification via the service worker when permission is granted (no push server). Web Push stays as best-effort for closed-app reach. Verified end-to-end: login, open stream, broadcast, event delivered.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:42:16 +03:30
soroush.asadi d2a7b18cb3 [UI] Fix navbar bell Razor leak, add Settings link, tidy desktop bar
CI/CD / CI · dotnet build (push) Successful in 1m13s
CI/CD / Deploy · hamkadr (push) Successful in 56s
Wrap the bell label in a span so the conditional parses as Razor code instead of leaking as literal text (Persian word char before the at-sign triggered email detection). Bell is icon-only on desktop, label shows in the mobile dropdown. Add a Settings link to admin actions so the demo-mode page is reachable from the navbar. Tighten desktop nav/action spacing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:06:44 +03:30