fix(web): persist uncertain birth time as a start/end clock window
Users pick a clock range instead of a coarse period plus notes, so rectification and window consult scan that range instead of a leftover afternoon bucket. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -49,26 +49,43 @@ export async function GET() {
|
||||
const admin = createAdminSupabaseClient();
|
||||
let { data: profile, error: profileError } = await supabase
|
||||
.from("profiles")
|
||||
.select("credits,active_birth_time,birth_time_status,birth_date,reported_birth_time,birth_time_source,birth_time_period,birth_time_clue,uncertainty_before_minutes,uncertainty_after_minutes,country_code,province_code,city_code,district_code,latitude,longitude,timezone_offset,birth_place_label,birth_place_type,birth_place_provider,birth_place_provider_id,timezone_id,timezone_source,name,birth_time,rectification_case_id")
|
||||
.select("credits,active_birth_time,birth_time_status,birth_date,reported_birth_time,birth_time_source,birth_time_period,declared_window_start,declared_window_end,birth_time_clue,uncertainty_before_minutes,uncertainty_after_minutes,country_code,province_code,city_code,district_code,latitude,longitude,timezone_offset,birth_place_label,birth_place_type,birth_place_provider,birth_place_provider_id,timezone_id,timezone_source,name,birth_time,rectification_case_id")
|
||||
.eq("id", userId)
|
||||
.single();
|
||||
|
||||
if (profileError && isMissingProfileColumn(profileError)) {
|
||||
const fallback = await supabase
|
||||
const withoutDeclaredWindow = await supabase
|
||||
.from("profiles")
|
||||
.select("credits,active_birth_time,birth_time_status,birth_date,reported_birth_time,birth_time_source,birth_time_period,birth_time_clue,uncertainty_before_minutes,uncertainty_after_minutes,country_code,province_code,city_code,district_code,latitude,longitude,timezone_offset,name,birth_time,rectification_case_id")
|
||||
.select("credits,active_birth_time,birth_time_status,birth_date,reported_birth_time,birth_time_source,birth_time_period,birth_time_clue,uncertainty_before_minutes,uncertainty_after_minutes,country_code,province_code,city_code,district_code,latitude,longitude,timezone_offset,birth_place_label,birth_place_type,birth_place_provider,birth_place_provider_id,timezone_id,timezone_source,name,birth_time,rectification_case_id")
|
||||
.eq("id", userId)
|
||||
.single();
|
||||
profile = fallback.data ? {
|
||||
...fallback.data,
|
||||
birth_place_label: undefined,
|
||||
birth_place_type: undefined,
|
||||
birth_place_provider: undefined,
|
||||
birth_place_provider_id: undefined,
|
||||
timezone_id: undefined,
|
||||
timezone_source: undefined,
|
||||
} : null;
|
||||
profileError = fallback.error;
|
||||
if (!withoutDeclaredWindow.error && withoutDeclaredWindow.data) {
|
||||
profile = {
|
||||
...withoutDeclaredWindow.data,
|
||||
declared_window_start: undefined,
|
||||
declared_window_end: undefined,
|
||||
};
|
||||
profileError = withoutDeclaredWindow.error;
|
||||
} else if (withoutDeclaredWindow.error && isMissingProfileColumn(withoutDeclaredWindow.error)) {
|
||||
const fallback = await supabase
|
||||
.from("profiles")
|
||||
.select("credits,active_birth_time,birth_time_status,birth_date,reported_birth_time,birth_time_source,birth_time_period,birth_time_clue,uncertainty_before_minutes,uncertainty_after_minutes,country_code,province_code,city_code,district_code,latitude,longitude,timezone_offset,name,birth_time,rectification_case_id")
|
||||
.eq("id", userId)
|
||||
.single();
|
||||
profile = fallback.data ? {
|
||||
...fallback.data,
|
||||
birth_place_label: undefined,
|
||||
birth_place_type: undefined,
|
||||
birth_place_provider: undefined,
|
||||
birth_place_provider_id: undefined,
|
||||
timezone_id: undefined,
|
||||
timezone_source: undefined,
|
||||
} : null;
|
||||
profileError = fallback.error;
|
||||
} else {
|
||||
profile = withoutDeclaredWindow.data;
|
||||
profileError = withoutDeclaredWindow.error;
|
||||
}
|
||||
}
|
||||
|
||||
if (profileError || !profile) {
|
||||
@@ -170,28 +187,45 @@ export async function PATCH(request: Request) {
|
||||
const admin = createAdminSupabaseClient();
|
||||
let { data: currentProfile, error: currentProfileError } = await admin
|
||||
.from("profiles")
|
||||
.select("birth_date,birth_time,reported_birth_time,active_birth_time,birth_time_source,birth_time_period,birth_time_clue,uncertainty_before_minutes,uncertainty_after_minutes,birth_time_status,rectification_case_id,country_code,province_code,city_code,district_code,latitude,longitude,timezone_offset,birth_place_label,birth_place_type,birth_place_provider,birth_place_provider_id,timezone_id,timezone_source")
|
||||
.select("birth_date,birth_time,reported_birth_time,active_birth_time,birth_time_source,birth_time_period,declared_window_start,declared_window_end,birth_time_clue,uncertainty_before_minutes,uncertainty_after_minutes,birth_time_status,rectification_case_id,country_code,province_code,city_code,district_code,latitude,longitude,timezone_offset,birth_place_label,birth_place_type,birth_place_provider,birth_place_provider_id,timezone_id,timezone_source")
|
||||
.eq("id", userId)
|
||||
.maybeSingle();
|
||||
if (currentProfileError && isMissingProfileColumn(currentProfileError)) {
|
||||
const fallback = await admin
|
||||
const withoutDeclaredWindow = await admin
|
||||
.from("profiles")
|
||||
.select("birth_date,birth_time,reported_birth_time,active_birth_time,birth_time_source,birth_time_period,birth_time_clue,uncertainty_before_minutes,uncertainty_after_minutes,birth_time_status,rectification_case_id,country_code,province_code,city_code,district_code")
|
||||
.select("birth_date,birth_time,reported_birth_time,active_birth_time,birth_time_source,birth_time_period,birth_time_clue,uncertainty_before_minutes,uncertainty_after_minutes,birth_time_status,rectification_case_id,country_code,province_code,city_code,district_code,latitude,longitude,timezone_offset,birth_place_label,birth_place_type,birth_place_provider,birth_place_provider_id,timezone_id,timezone_source")
|
||||
.eq("id", userId)
|
||||
.maybeSingle();
|
||||
currentProfile = fallback.data ? {
|
||||
...fallback.data,
|
||||
latitude: undefined,
|
||||
longitude: undefined,
|
||||
timezone_offset: undefined,
|
||||
birth_place_label: undefined,
|
||||
birth_place_type: undefined,
|
||||
birth_place_provider: undefined,
|
||||
birth_place_provider_id: undefined,
|
||||
timezone_id: undefined,
|
||||
timezone_source: undefined,
|
||||
} : null;
|
||||
currentProfileError = fallback.error;
|
||||
if (!withoutDeclaredWindow.error && withoutDeclaredWindow.data) {
|
||||
currentProfile = {
|
||||
...withoutDeclaredWindow.data,
|
||||
declared_window_start: undefined,
|
||||
declared_window_end: undefined,
|
||||
};
|
||||
currentProfileError = withoutDeclaredWindow.error;
|
||||
} else if (withoutDeclaredWindow.error && isMissingProfileColumn(withoutDeclaredWindow.error)) {
|
||||
const fallback = await admin
|
||||
.from("profiles")
|
||||
.select("birth_date,birth_time,reported_birth_time,active_birth_time,birth_time_source,birth_time_period,birth_time_clue,uncertainty_before_minutes,uncertainty_after_minutes,birth_time_status,rectification_case_id,country_code,province_code,city_code,district_code")
|
||||
.eq("id", userId)
|
||||
.maybeSingle();
|
||||
currentProfile = fallback.data ? {
|
||||
...fallback.data,
|
||||
latitude: undefined,
|
||||
longitude: undefined,
|
||||
timezone_offset: undefined,
|
||||
birth_place_label: undefined,
|
||||
birth_place_type: undefined,
|
||||
birth_place_provider: undefined,
|
||||
birth_place_provider_id: undefined,
|
||||
timezone_id: undefined,
|
||||
timezone_source: undefined,
|
||||
} : null;
|
||||
currentProfileError = fallback.error;
|
||||
} else {
|
||||
currentProfile = withoutDeclaredWindow.data;
|
||||
currentProfileError = withoutDeclaredWindow.error;
|
||||
}
|
||||
}
|
||||
if (currentProfileError) {
|
||||
return NextResponse.json({ error: "暂时无法核对现有出生资料" }, { status: 500 });
|
||||
@@ -210,6 +244,12 @@ export async function PATCH(request: Request) {
|
||||
...(payload.birth_time_period !== undefined
|
||||
? { birth_time_period: payload.birth_time_period }
|
||||
: {}),
|
||||
...(payload.declared_window_start !== undefined
|
||||
? { declared_window_start: payload.declared_window_start }
|
||||
: {}),
|
||||
...(payload.declared_window_end !== undefined
|
||||
? { declared_window_end: payload.declared_window_end }
|
||||
: {}),
|
||||
...(payload.birth_time_clue !== undefined
|
||||
? { birth_time_clue: payload.birth_time_clue }
|
||||
: {}),
|
||||
@@ -261,6 +301,30 @@ export async function PATCH(request: Request) {
|
||||
data = fallback.data;
|
||||
error = fallback.error;
|
||||
}
|
||||
if (error && isMissingProfileColumn(error)) {
|
||||
const {
|
||||
declared_window_start: _start,
|
||||
declared_window_end: _end,
|
||||
...withoutDeclaredWindow
|
||||
} = withCoordinates;
|
||||
void _start;
|
||||
void _end;
|
||||
const fallback = await writeProfile(withoutDeclaredWindow);
|
||||
data = fallback.data;
|
||||
error = fallback.error;
|
||||
}
|
||||
if (error && isMissingProfileColumn(error)) {
|
||||
const {
|
||||
declared_window_start: _start,
|
||||
declared_window_end: _end,
|
||||
...withoutCoordinatesOrWindow
|
||||
} = withoutCoordinates;
|
||||
void _start;
|
||||
void _end;
|
||||
const fallback = await writeProfile(withoutCoordinatesOrWindow);
|
||||
data = fallback.data;
|
||||
error = fallback.error;
|
||||
}
|
||||
|
||||
if (!error && !data && invalidatesUnconfirmedApplication) {
|
||||
return NextResponse.json({
|
||||
|
||||
@@ -341,7 +341,7 @@ export async function POST(request: Request) {
|
||||
async loadProfile(profileUserId) {
|
||||
const { data, error } = await supabase
|
||||
.from("profiles")
|
||||
.select("name,birth_date,reported_birth_time,active_birth_time,birth_time_source,birth_time_period,birth_time_status,country_code,province_code,city_code,district_code,latitude,longitude,timezone_offset,birth_place_label,birth_place_type,birth_place_provider,birth_place_provider_id,timezone_id,timezone_source")
|
||||
.select("name,birth_date,reported_birth_time,active_birth_time,birth_time_source,birth_time_period,declared_window_start,declared_window_end,birth_time_status,country_code,province_code,city_code,district_code,latitude,longitude,timezone_offset,birth_place_label,birth_place_type,birth_place_provider,birth_place_provider_id,timezone_id,timezone_source")
|
||||
.eq("id", profileUserId)
|
||||
.single();
|
||||
if (error || !data) throw new ConsultationProfileTruthError("profile_unavailable");
|
||||
|
||||
@@ -20,7 +20,7 @@ function createProfileRepository(
|
||||
async loadProfile(userId) {
|
||||
return admin
|
||||
.from("profiles")
|
||||
.select("id,name,birth_date,birth_time,reported_birth_time,active_birth_time,birth_time_source,birth_time_period,birth_time_clue,uncertainty_before_minutes,uncertainty_after_minutes,birth_time_status,country_code,province_code,city_code,district_code,latitude,longitude,timezone_offset,birth_place_label,birth_place_type,birth_place_provider,birth_place_provider_id,timezone_id,timezone_source,onboarding_payload,onboarding_version,onboarding_generated_at")
|
||||
.select("id,name,birth_date,birth_time,reported_birth_time,active_birth_time,birth_time_source,birth_time_period,declared_window_start,declared_window_end,birth_time_clue,uncertainty_before_minutes,uncertainty_after_minutes,birth_time_status,country_code,province_code,city_code,district_code,latitude,longitude,timezone_offset,birth_place_label,birth_place_type,birth_place_provider,birth_place_provider_id,timezone_id,timezone_source,onboarding_payload,onboarding_version,onboarding_generated_at")
|
||||
.eq("id", userId)
|
||||
.maybeSingle();
|
||||
},
|
||||
|
||||
@@ -2050,8 +2050,17 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
|
||||
.birth-time-source-option b { font-size: var(--type-body-sm); line-height: 1.35; }
|
||||
.birth-time-source-option small { font-size: var(--type-caption); line-height: 1.5; }
|
||||
.birth-time-detail-grid { grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr); gap: var(--space-3); padding: var(--space-4); border: 1px solid var(--color-border); border-radius: var(--radius-lg); background: var(--color-canvas-soft); }
|
||||
.birth-time-period-details { align-items: start; }
|
||||
.birth-time-period-details > label { align-self: start; }
|
||||
.birth-time-window-details { align-items: start; }
|
||||
.birth-time-window-details > label { align-self: start; }
|
||||
.birth-time-window-intro {
|
||||
grid-column: 1 / -1;
|
||||
margin: 0;
|
||||
color: var(--color-ink-secondary);
|
||||
font-size: var(--type-caption);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.birth-time-window-details .birth-time-skip-button { grid-column: 1 / -1; justify-self: start; min-height: 40px; padding-inline: 13px; border-color: transparent; background: transparent; color: var(--color-action); font-size: var(--type-caption); }
|
||||
.birth-time-window-details .birth-time-skip-button:hover { background: color-mix(in srgb, var(--color-action-soft) 72%, transparent); }
|
||||
.birth-time-detail-grid > label { gap: 7px; }
|
||||
.birth-time-detail-grid > label > span { color: var(--color-ink-secondary); font-size: var(--type-caption); font-weight: 700; }
|
||||
.birth-time-detail-grid textarea {
|
||||
@@ -2081,8 +2090,6 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
|
||||
.birth-time-detail-grid textarea::-webkit-resizer {
|
||||
background: color-mix(in srgb, var(--color-border-strong) 70%, transparent);
|
||||
}
|
||||
.birth-time-period-details .birth-time-skip-button { grid-column: 1 / -1; justify-self: start; min-height: 40px; padding-inline: 13px; border-color: transparent; background: transparent; color: var(--color-action); font-size: var(--type-caption); }
|
||||
.birth-time-period-details .birth-time-skip-button:hover { background: color-mix(in srgb, var(--color-action-soft) 72%, transparent); }
|
||||
.birth-time-detail-note[role="status"] { display: grid; gap: var(--space-3); padding: var(--space-4); border: 1px solid var(--color-border); border-radius: var(--radius-md); background: var(--color-canvas-soft); }
|
||||
.birth-time-detail-note[role="status"] p { margin: 0; }
|
||||
.onboarding-card-actions { align-items: center; justify-content: space-between; gap: var(--space-4); padding-top: var(--space-2); }
|
||||
@@ -2091,7 +2098,7 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
|
||||
@media (max-width: 620px) {
|
||||
.onboarding-card-heading { align-items: flex-start; flex-direction: column; gap: var(--space-2); }
|
||||
.birth-time-source-list, .birth-time-detail-grid { grid-template-columns: 1fr; }
|
||||
.birth-time-period-details .birth-time-skip-button { grid-column: auto; justify-self: stretch; }
|
||||
.birth-time-window-details .birth-time-skip-button { grid-column: auto; justify-self: stretch; }
|
||||
.onboarding-card-actions { align-items: stretch; flex-direction: column; }
|
||||
.onboarding-card-actions .button-primary { width: 100%; }
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ import {
|
||||
birthTimePersistenceValues,
|
||||
declaredBirthInputChanged,
|
||||
describeBirthTimeDraft,
|
||||
hydrateDeclaredWindowDraft,
|
||||
isDeclaredBirthProfileComplete,
|
||||
isBirthTimeDraftReady,
|
||||
birthTimeDraftReadyHint,
|
||||
@@ -358,6 +359,8 @@ const emptyProfile: Profile = {
|
||||
reportedTime: "",
|
||||
birthTimeSource: "",
|
||||
birthTimePeriod: "",
|
||||
declaredWindowStart: "",
|
||||
declaredWindowEnd: "",
|
||||
birthTimeClue: "",
|
||||
uncertaintyBeforeMinutes: null,
|
||||
uncertaintyAfterMinutes: null,
|
||||
@@ -659,6 +662,8 @@ function readProfile(value: unknown): Profile {
|
||||
active_birth_time?: unknown;
|
||||
birth_time_source?: unknown;
|
||||
birth_time_period?: unknown;
|
||||
declared_window_start?: unknown;
|
||||
declared_window_end?: unknown;
|
||||
birth_time_clue?: unknown;
|
||||
uncertainty_before_minutes?: unknown;
|
||||
uncertainty_after_minutes?: unknown;
|
||||
@@ -696,6 +701,13 @@ function readProfile(value: unknown): Profile {
|
||||
const reportedTime = persistedReportedTime || (source === "legacy_import" ? time : "");
|
||||
const knownPeriods = ["early_morning", "morning", "afternoon", "evening", "late_night"] as const;
|
||||
const period = knownPeriods.find((item) => item === profile.birth_time_period) ?? "";
|
||||
const windowStart = typeof profile.declared_window_start === "string" ? profile.declared_window_start.slice(0, 5) : "";
|
||||
const windowEnd = typeof profile.declared_window_end === "string" ? profile.declared_window_end.slice(0, 5) : "";
|
||||
const declaredWindow = hydrateDeclaredWindowDraft({
|
||||
period,
|
||||
start: windowStart,
|
||||
end: windowEnd,
|
||||
});
|
||||
const knownStatuses = ["reported", "assessing", "rectifying", "candidate", "accepted", "confirmed"] as const;
|
||||
const status = knownStatuses.find((item) => item === profile.birth_time_status)
|
||||
?? (time ? "confirmed" : "");
|
||||
@@ -723,8 +735,10 @@ function readProfile(value: unknown): Profile {
|
||||
time: typeof time === "string" ? time : "",
|
||||
reportedTime: typeof reportedTime === "string" ? reportedTime : "",
|
||||
birthTimeSource: source,
|
||||
birthTimePeriod: period,
|
||||
birthTimeClue: typeof profile.birth_time_clue === "string" ? profile.birth_time_clue.slice(0, 240) : "",
|
||||
birthTimePeriod: declaredWindow.birthTimePeriod,
|
||||
declaredWindowStart: declaredWindow.declaredWindowStart,
|
||||
declaredWindowEnd: declaredWindow.declaredWindowEnd,
|
||||
birthTimeClue: "",
|
||||
uncertaintyBeforeMinutes: typeof profile.uncertainty_before_minutes === "number" ? profile.uncertainty_before_minutes : null,
|
||||
uncertaintyAfterMinutes: typeof profile.uncertainty_after_minutes === "number" ? profile.uncertainty_after_minutes : null,
|
||||
birthTimeStatus: status,
|
||||
@@ -1475,7 +1489,9 @@ export default function Home() {
|
||||
time: isCompletedCandidatePreview ? "04:53" : isRectificationPreview || isAssessmentLoadingPreview ? "" : "12:30",
|
||||
reportedTime: isRectificationPreview ? "14:30" : isAssessmentLoadingPreview || isCompletedCandidatePreview ? "" : "12:30",
|
||||
birthTimeSource: isRectificationPreview ? "approximate" : isAssessmentLoadingPreview || isCompletedCandidatePreview ? "period_only" : "legacy_import",
|
||||
birthTimePeriod: isAssessmentLoadingPreview || isCompletedCandidatePreview ? "early_morning" : "",
|
||||
...hydrateDeclaredWindowDraft({
|
||||
period: isAssessmentLoadingPreview || isCompletedCandidatePreview ? "early_morning" : "",
|
||||
}),
|
||||
birthTimeClue: "",
|
||||
uncertaintyBeforeMinutes: isRectificationPreview ? 30 : null,
|
||||
uncertaintyAfterMinutes: isRectificationPreview ? 30 : null,
|
||||
|
||||
Reference in New Issue
Block a user