feat(npi): add NpiIntakeStep wizard + 6 healthcare order pages
This commit is contained in:
parent
e67db156e8
commit
f349d519c6
8 changed files with 415 additions and 1 deletions
|
|
@ -42,6 +42,7 @@ import OCNStep from "./steps/OCNStep.astro";
|
|||
import MCS150Step from "./steps/MCS150Step.astro";
|
||||
import DOTIntakeStep from "./steps/DOTIntakeStep.astro";
|
||||
import StateTruckingIntakeStep from "./steps/StateTruckingIntakeStep.astro";
|
||||
import NpiIntakeStep from "./steps/NpiIntakeStep.astro";
|
||||
import ClassificationWizard from "./steps/ClassificationWizard.astro";
|
||||
import ReviewStep from "./steps/ReviewStep.astro";
|
||||
import PaymentStep from "./steps/PaymentStep.astro";
|
||||
|
|
@ -81,6 +82,7 @@ const STEP_LABELS: Record<string, string> = {
|
|||
ocn: "OCN Details",
|
||||
"dot-intake": "Carrier Details",
|
||||
"state-trucking": "Filing Details",
|
||||
"npi-intake": "Provider Details",
|
||||
review: "Review",
|
||||
payment: "Payment",
|
||||
};
|
||||
|
|
@ -126,6 +128,7 @@ const STEP_LABELS: Record<string, string> = {
|
|||
{steps.includes("mcs150") && <div data-step="mcs150" hidden><MCS150Step /></div>}
|
||||
{steps.includes("dot-intake") && <div data-step="dot-intake" hidden><DOTIntakeStep /></div>}
|
||||
{steps.includes("state-trucking") && <div data-step="state-trucking" hidden><StateTruckingIntakeStep /></div>}
|
||||
{steps.includes("npi-intake") && <div data-step="npi-intake" hidden><NpiIntakeStep /></div>}
|
||||
{steps.includes("classification") && <div data-step="classification" hidden><ClassificationWizard /></div>}
|
||||
{steps.includes("review") && <div data-step="review" hidden><ReviewStep service_slug={service_slug} /></div>}
|
||||
{steps.includes("payment") && <div data-step="payment" hidden><PaymentStep service_slug={service_slug} /></div>}
|
||||
|
|
@ -235,7 +238,7 @@ const STEP_LABELS: Record<string, string> = {
|
|||
block6_cert: "Certifications", bdc_data: "BDC Data",
|
||||
stir_shaken: "STIR/SHAKEN", calea: "CALEA",
|
||||
foreign_carrier: "Foreign Affiliation", foreign_qual: "State Registration", dc_agent: "D.C. Agent", cpni_questions: "CPNI Details", cdr_period: "Reporting Period",
|
||||
ocn: "OCN Details", "dot-intake": "Carrier Details", "state-trucking": "Filing Details", review: "Review", payment: "Payment",
|
||||
ocn: "OCN Details", "dot-intake": "Carrier Details", "state-trucking": "Filing Details", "npi-intake": "Provider Details", review: "Review", payment: "Payment",
|
||||
};
|
||||
|
||||
// Category-gated dynamic step insertion. After the user picks Line 105
|
||||
|
|
|
|||
183
site/src/components/intake/steps/NpiIntakeStep.astro
Normal file
183
site/src/components/intake/steps/NpiIntakeStep.astro
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
---
|
||||
// Healthcare / NPI provider intake. Collects the provider identity (NPI,
|
||||
// name, email) plus optional PECOS / specialty / practice fields each NPI
|
||||
// handler uses. Mirrors StateTruckingIntakeStep.astro's collect pattern.
|
||||
---
|
||||
|
||||
<div class="pw-step" data-slug="npi-intake">
|
||||
<h2>Provider Information</h2>
|
||||
<p class="pw-help">
|
||||
Tell us about the provider so we can prepare your CMS filing. We file on your behalf.
|
||||
</p>
|
||||
|
||||
<div class="pw-form-grid">
|
||||
<h3>Provider Details</h3>
|
||||
<div class="pw-row-2">
|
||||
<label class="pw-field"><span>Provider / Organization Name <em>*</em></span>
|
||||
<input type="text" id="npi-provider-name" required placeholder="As registered in NPPES" /></label>
|
||||
<label class="pw-field"><span>NPI (10-digit) <em>*</em></span>
|
||||
<input type="text" id="npi-number" required maxlength="10" inputmode="numeric" placeholder="e.g. 1234567893" /></label>
|
||||
</div>
|
||||
<div class="pw-row-2">
|
||||
<label class="pw-field"><span>Email <em>*</em></span>
|
||||
<input type="email" id="npi-email" required placeholder="you@practice.com" /></label>
|
||||
<label class="pw-field"><span>Practice State</span>
|
||||
<select id="npi-practice-state">
|
||||
<option value="">--</option>
|
||||
<option>AL</option><option>AK</option><option>AZ</option><option>AR</option><option>CA</option><option>CO</option><option>CT</option><option>DE</option><option>FL</option><option>GA</option><option>HI</option><option>ID</option><option>IL</option><option>IN</option><option>IA</option><option>KS</option><option>KY</option><option>LA</option><option>ME</option><option>MD</option><option>MA</option><option>MI</option><option>MN</option><option>MS</option><option>MO</option><option>MT</option><option>NE</option><option>NV</option><option>NH</option><option>NJ</option><option>NM</option><option>NY</option><option>NC</option><option>ND</option><option>OH</option><option>OK</option><option>OR</option><option>PA</option><option>RI</option><option>SC</option><option>SD</option><option>TN</option><option>TX</option><option>UT</option><option>VT</option><option>VA</option><option>WA</option><option>WV</option><option>WI</option><option>WY</option><option>DC</option>
|
||||
</select></label>
|
||||
</div>
|
||||
<div class="pw-row-2">
|
||||
<label class="pw-field"><span>PECOS Enrollment ID <span class="pw-opt">(if known)</span></span>
|
||||
<input type="text" id="npi-pecos-id" placeholder="e.g. I20040309000221" /></label>
|
||||
<label class="pw-field"><span>Specialty / Taxonomy <span class="pw-opt">(optional)</span></span>
|
||||
<input type="text" id="npi-specialty" placeholder="e.g. Internal Medicine" /></label>
|
||||
</div>
|
||||
|
||||
<!-- Reactivation-specific -->
|
||||
<div id="npi-sec-reactivation" hidden>
|
||||
<h3>Deactivation Details</h3>
|
||||
<div class="pw-row">
|
||||
<label class="pw-field"><span>Reason your NPI was deactivated <span class="pw-opt">(if known)</span></span>
|
||||
<input type="text" id="npi-deactivation-reason" placeholder="e.g. non-response to revalidation, voluntary, etc." /></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- NPPES update-specific -->
|
||||
<div id="npi-sec-nppes" hidden>
|
||||
<h3>What needs updating?</h3>
|
||||
<div class="pw-row">
|
||||
<label class="pw-field"><span>Fields to update</span>
|
||||
<input type="text" id="npi-fields-to-update" placeholder="e.g. practice address, phone, taxonomy, authorized official" /></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Screening-specific -->
|
||||
<div id="npi-sec-screening" hidden>
|
||||
<h3>Screening Scope</h3>
|
||||
<div class="pw-row-2">
|
||||
<label class="pw-field"><span>Organization Name <span class="pw-opt">(optional)</span></span>
|
||||
<input type="text" id="npi-org-name" placeholder="Practice / group name" /></label>
|
||||
<label class="pw-field"><span>Staff to screen <span class="pw-opt">(count)</span></span>
|
||||
<input type="number" id="npi-staff-count" min="0" placeholder="e.g. 5" /></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="pw-npi-errors" class="pw-err" hidden></div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.pw-step h2 { color: #0f766e; margin: 0 0 0.25rem; }
|
||||
.pw-help { color: #64748b; margin: 0 0 1.25rem; }
|
||||
.pw-form-grid h3 { font-size: 1rem; color: #134e4a; margin: 1.25rem 0 0.5rem; border-bottom: 1px solid #ccfbf1; padding-bottom: 0.25rem; }
|
||||
.pw-row { display: grid; grid-template-columns: 1fr; gap: 0.75rem; margin-bottom: 0.5rem; }
|
||||
.pw-row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; margin-bottom: 0.5rem; }
|
||||
.pw-field { display: flex; flex-direction: column; gap: 0.25rem; }
|
||||
.pw-field span { font-size: 0.85rem; font-weight: 600; color: #374151; }
|
||||
.pw-field em { color: #dc2626; font-style: normal; }
|
||||
.pw-opt { font-weight: 400; color: #94a3b8; }
|
||||
.pw-field input, .pw-field select { padding: 0.55rem 0.7rem; border: 1px solid #cbd5e1; border-radius: 6px; font-size: 0.95rem; }
|
||||
.pw-field input:focus, .pw-field select:focus { outline: none; border-color: #14b8a6; box-shadow: 0 0 0 2px rgba(20,184,166,0.2); }
|
||||
.pw-err { color: #b91c1c; margin-top: 0.75rem; font-size: 0.9rem; background: #fee2e2; padding: 0.5rem 0.75rem; border-radius: 6px; }
|
||||
@media (max-width: 640px) { .pw-row-2 { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
|
||||
<script is:inline>
|
||||
if (!document.querySelector('[data-slug="npi-intake"], [data-step="npi-intake"]')) {
|
||||
// Not an NPI intake page — skip
|
||||
} else {
|
||||
|
||||
// Which extra sections to show per slug.
|
||||
const NPI_SECTIONS = {
|
||||
"npi-reactivation": ["npi-sec-reactivation"],
|
||||
"nppes-update": ["npi-sec-nppes"],
|
||||
"oig-sam-screening": ["npi-sec-screening"],
|
||||
"provider-compliance-bundle": ["npi-sec-screening"],
|
||||
};
|
||||
const ALL_NPI_SECTIONS = ["npi-sec-reactivation","npi-sec-nppes","npi-sec-screening"];
|
||||
|
||||
function showNpiSections() {
|
||||
const PW = window.PWIntake;
|
||||
const state = PW?.get?.() || {};
|
||||
const wizardEl = document.querySelector(".pw-wizard[data-service]");
|
||||
const pageSlug = wizardEl?.getAttribute("data-service") || "";
|
||||
const slugs = state.batch_slugs || [pageSlug || state.service_slug || ""];
|
||||
const show = new Set();
|
||||
for (const slug of slugs) for (const sec of (NPI_SECTIONS[slug] || [])) show.add(sec);
|
||||
for (const id of ALL_NPI_SECTIONS) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.hidden = !show.has(id);
|
||||
}
|
||||
}
|
||||
|
||||
function initNpiSections() {
|
||||
const wizardEl = document.querySelector(".pw-wizard[data-service]");
|
||||
if (!wizardEl) { setTimeout(initNpiSections, 100); return; }
|
||||
showNpiSections();
|
||||
}
|
||||
initNpiSections();
|
||||
|
||||
window.addEventListener("pw:step-shown", (evt) => {
|
||||
if (evt.detail.step !== "npi-intake") return;
|
||||
showNpiSections();
|
||||
const s = window.PWIntake.get();
|
||||
const d = s.intake_data || {};
|
||||
const map = {
|
||||
"npi-provider-name": d.provider_name || s.name || "",
|
||||
"npi-number": d.npi || "",
|
||||
"npi-email": d.email || s.email || "",
|
||||
"npi-practice-state": d.practice_state || "",
|
||||
"npi-pecos-id": d.pecos_enrollment_id || "",
|
||||
"npi-specialty": d.specialty || "",
|
||||
"npi-deactivation-reason": d.deactivation_reason || "",
|
||||
"npi-fields-to-update": d.fields_to_update || "",
|
||||
"npi-org-name": d.organization_name || "",
|
||||
"npi-staff-count": d.staff_count || "",
|
||||
};
|
||||
for (const [id, val] of Object.entries(map)) {
|
||||
const el = document.getElementById(id);
|
||||
if (el && val) el.value = val;
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("pw:step-next", (evt) => {
|
||||
const PW = window.PWIntake;
|
||||
if (PW.steps[PW.get().step_index] !== "npi-intake") return;
|
||||
const errDiv = document.getElementById("pw-npi-errors");
|
||||
errDiv.hidden = true;
|
||||
const val = (id) => (document.getElementById(id))?.value?.trim() || "";
|
||||
|
||||
const missing = [];
|
||||
if (!val("npi-provider-name")) missing.push("Provider / Organization Name");
|
||||
const npi = val("npi-number");
|
||||
if (!npi) missing.push("NPI");
|
||||
else if (!/^\d{10}$/.test(npi)) missing.push("NPI must be 10 digits");
|
||||
const email = val("npi-email");
|
||||
if (!email) missing.push("Email");
|
||||
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) missing.push("a valid Email");
|
||||
|
||||
if (missing.length) {
|
||||
evt.preventDefault();
|
||||
errDiv.hidden = false;
|
||||
errDiv.textContent = "Please fix: " + missing.join(", ");
|
||||
return;
|
||||
}
|
||||
|
||||
const state = PW.get();
|
||||
PW.set({ ...state, intake_data: { ...state.intake_data,
|
||||
provider_name: val("npi-provider-name"),
|
||||
npi: npi,
|
||||
email: email,
|
||||
practice_state: val("npi-practice-state"),
|
||||
pecos_enrollment_id: val("npi-pecos-id"),
|
||||
specialty: val("npi-specialty"),
|
||||
deactivation_reason: val("npi-deactivation-reason"),
|
||||
fields_to_update: val("npi-fields-to-update"),
|
||||
organization_name: val("npi-org-name"),
|
||||
staff_count: val("npi-staff-count"),
|
||||
}});
|
||||
});
|
||||
|
||||
} // end guard
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue