fix(parties): one "från SCB" note per contact section (#2316)
* fix(parties): one "från SCB" note per contact section, not one under every field The founder read four tags in a row. Kontaktuppgifter now ends with one sentence naming the fields the register gave: "E-post, Telefon, adress och Momsnr från SCB." Nothing else changes; the fill rule and the equality test behind it are the same. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(parties): the customer page's registry note tolerates the loading state Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(parties): the supplier page's registry note tolerates the loading state Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
Jakob Wennberg
parent
971952fe19
commit
bff44e5757
@@ -28,7 +28,7 @@ import type { Customer, CustomerType, CreateCustomerInput } from '@/types'
|
||||
import { DetailPageSkeleton } from '@/components/common/DetailPageSkeleton'
|
||||
import { PartyFactsSection } from '@/components/parties/PartyFactsSection'
|
||||
import { usePartyDossier } from '@/components/parties/use-party-dossier'
|
||||
import { fromRegistry, addressRowsFromRegistry } from '@/lib/parties/registry-summary'
|
||||
import { fromRegistry, addressRowsFromRegistry, listSv } from '@/lib/parties/registry-summary'
|
||||
|
||||
const CUSTOMER_TYPE_KEY: Record<CustomerType, string> = {
|
||||
individual: 'type_individual',
|
||||
@@ -68,7 +68,17 @@ export default function CustomerDetailPage({
|
||||
const partyId = customer && customer.customer_type !== 'individual' ? ((customer as { party_id?: string | null }).party_id ?? null) : null
|
||||
const party = usePartyDossier(partyId)
|
||||
const registryAddress = party.registry?.contact.address ? addressRowsFromRegistry(party.registry.contact.address) : null
|
||||
const scbNote = (isFromRegistry: boolean) => (isFromRegistry ? <span className="block text-xs text-muted-foreground">{tParties('facts_from_registry')}</span> : null)
|
||||
// Which contact fields carry what the register said: one note for the
|
||||
// section, not a tag under every row.
|
||||
const registryFields = [
|
||||
fromRegistry(customer?.email, party.registry?.contact.email) ? tParties('fact_email') : null,
|
||||
fromRegistry(customer?.phone, party.registry?.contact.phone) ? tParties('fact_phone') : null,
|
||||
!!registryAddress && fromRegistry(customer?.address_line1, registryAddress.address_line1) && fromRegistry(customer?.city, registryAddress.city) ? tParties('facts_address_short') : null,
|
||||
fromRegistry(customer?.vat_number, party.registry?.vat_number) ? tParties('fact_vat') : null,
|
||||
].filter((x): x is string => !!x)
|
||||
const registryNote = registryFields.length ? (
|
||||
<p className="pt-2 text-xs text-muted-foreground">{tParties('facts_contact_from_registry', { fields: listSv(registryFields, tParties('facts_list_and')) })}</p>
|
||||
) : null
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [isEditOpen, setIsEditOpen] = useState(false)
|
||||
const [isUpdating, setIsUpdating] = useState(false)
|
||||
@@ -271,11 +281,9 @@ export default function CustomerDetailPage({
|
||||
) : (
|
||||
<DefEmpty />
|
||||
)}
|
||||
{scbNote(fromRegistry(customer.email, party.registry?.contact.email))}
|
||||
</DefRow>
|
||||
<DefRow label={t('def_phone')}>
|
||||
{customer.phone || <DefEmpty />}
|
||||
{scbNote(fromRegistry(customer.phone, party.registry?.contact.phone))}
|
||||
</DefRow>
|
||||
<DefRow label={t('def_address')}>
|
||||
{customer.address_line1 || customer.city ? (
|
||||
@@ -285,17 +293,13 @@ export default function CustomerDetailPage({
|
||||
{(customer.postal_code || customer.city) && (
|
||||
<p>{[customer.postal_code, customer.city].filter(Boolean).join(' ')}</p>
|
||||
)}
|
||||
{scbNote(
|
||||
!!registryAddress &&
|
||||
fromRegistry(customer.address_line1, registryAddress.address_line1) &&
|
||||
fromRegistry(customer.city, registryAddress.city),
|
||||
)}
|
||||
{customer.country && <p>{getCountryName(customer.country, errorLocale)}</p>}
|
||||
</div>
|
||||
) : (
|
||||
<DefEmpty />
|
||||
)}
|
||||
</DefRow>
|
||||
{registryNote}
|
||||
</DetailSection>
|
||||
|
||||
{partyId && party.dossier ? (
|
||||
|
||||
@@ -21,7 +21,7 @@ import type { Supplier, SupplierType, CreateSupplierInput, SupplierInvoice } fro
|
||||
import { DetailPageSkeleton } from '@/components/common/DetailPageSkeleton'
|
||||
import { PartyFactsSection } from '@/components/parties/PartyFactsSection'
|
||||
import { usePartyDossier } from '@/components/parties/use-party-dossier'
|
||||
import { fromRegistry, addressRowsFromRegistry } from '@/lib/parties/registry-summary'
|
||||
import { fromRegistry, addressRowsFromRegistry, listSv } from '@/lib/parties/registry-summary'
|
||||
import { formatOrgNumber } from '@/lib/utils'
|
||||
|
||||
// Supplier invoices carry their own currency; "kr" is only correct for SEK.
|
||||
@@ -51,7 +51,17 @@ export default function SupplierDetailPage() {
|
||||
const partyId = (supplier as { party_id?: string | null } | null)?.party_id ?? null
|
||||
const party = usePartyDossier(partyId)
|
||||
const registryAddress = party.registry?.contact.address ? addressRowsFromRegistry(party.registry.contact.address) : null
|
||||
const scbNote = (isFromRegistry: boolean) => (isFromRegistry ? <span className="block text-xs text-muted-foreground">{tParties('facts_from_registry')}</span> : null)
|
||||
// Which contact fields carry what the register said: one note for the
|
||||
// section, not a tag under every row.
|
||||
const registryFields = [
|
||||
fromRegistry(supplier?.email, party.registry?.contact.email) ? tParties('fact_email') : null,
|
||||
fromRegistry(supplier?.phone, party.registry?.contact.phone) ? tParties('fact_phone') : null,
|
||||
!!registryAddress && fromRegistry(supplier?.address_line1, registryAddress.address_line1) && fromRegistry(supplier?.city, registryAddress.city) ? tParties('facts_address_short') : null,
|
||||
fromRegistry(supplier?.vat_number, party.registry?.vat_number) ? tParties('fact_vat') : null,
|
||||
].filter((x): x is string => !!x)
|
||||
const registryNote = registryFields.length ? (
|
||||
<p className="pt-2 text-xs text-muted-foreground">{tParties('facts_contact_from_registry', { fields: listSv(registryFields, tParties('facts_list_and')) })}</p>
|
||||
) : null
|
||||
const [invoices, setInvoices] = useState<SupplierInvoice[]>([])
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [isEditOpen, setIsEditOpen] = useState(false)
|
||||
@@ -290,11 +300,9 @@ export default function SupplierDetailPage() {
|
||||
<DetailSection kicker={t('contact_section_title')}>
|
||||
<DefRow label={t('def_email')}>
|
||||
{supplier.email || <DefEmpty />}
|
||||
{scbNote(fromRegistry(supplier.email, party.registry?.contact.email))}
|
||||
</DefRow>
|
||||
<DefRow label={t('def_phone')}>
|
||||
{supplier.phone || <DefEmpty />}
|
||||
{scbNote(fromRegistry(supplier.phone, party.registry?.contact.phone))}
|
||||
</DefRow>
|
||||
<DefRow label={t('def_address')}>
|
||||
{supplier.address_line1 || supplier.city ? (
|
||||
@@ -304,11 +312,6 @@ export default function SupplierDetailPage() {
|
||||
{(supplier.postal_code || supplier.city) && (
|
||||
<p>{[supplier.postal_code, supplier.city].filter(Boolean).join(' ')}</p>
|
||||
)}
|
||||
{scbNote(
|
||||
!!registryAddress &&
|
||||
fromRegistry(supplier.address_line1, registryAddress.address_line1) &&
|
||||
fromRegistry(supplier.city, registryAddress.city),
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<DefEmpty />
|
||||
@@ -317,9 +320,9 @@ export default function SupplierDetailPage() {
|
||||
{supplier.vat_number && (
|
||||
<DefRow label={t('def_vat')}>
|
||||
{supplier.vat_number}
|
||||
{scbNote(fromRegistry(supplier.vat_number, party.registry?.vat_number))}
|
||||
</DefRow>
|
||||
)}
|
||||
{registryNote}
|
||||
</DetailSection>
|
||||
|
||||
<DetailSection kicker={t('payment_section_title')}>
|
||||
|
||||
@@ -149,3 +149,9 @@ export function contactFill(row: ContactRow, now: RegistrySummary['contact'], be
|
||||
export function fromRegistry(rowValue: string | null | undefined, registryValue: string | null | undefined): boolean {
|
||||
return !!norm(rowValue) && norm(rowValue) === norm(registryValue)
|
||||
}
|
||||
|
||||
/** "E-post, telefon och adress": a list the way Swedish (or English) joins it. */
|
||||
export function listSv(items: string[], and: string): string {
|
||||
if (items.length <= 1) return items.join('')
|
||||
return `${items.slice(0, -1).join(', ')} ${and} ${items[items.length - 1]}`
|
||||
}
|
||||
|
||||
@@ -8722,6 +8722,9 @@
|
||||
"facts_from_registry": "from SCB",
|
||||
"facts_filled_title": "Contact details filled in from SCB",
|
||||
"facts_filled_description": "{fields}. Edit freely; what you type is not replaced on the next fetch.",
|
||||
"facts_contact_from_registry": "{fields} from SCB.",
|
||||
"facts_address_short": "address",
|
||||
"facts_list_and": "and",
|
||||
"fact_trade_name": "Trade name",
|
||||
"open_dossier": "Open {name}",
|
||||
"attn_create": "Create suggestions",
|
||||
|
||||
@@ -8722,6 +8722,9 @@
|
||||
"facts_from_registry": "från SCB",
|
||||
"facts_filled_title": "Kontaktuppgifter ifyllda från SCB",
|
||||
"facts_filled_description": "{fields}. Ändra fritt; det du skriver ersätts inte vid nästa hämtning.",
|
||||
"facts_contact_from_registry": "{fields} från SCB.",
|
||||
"facts_address_short": "adress",
|
||||
"facts_list_and": "och",
|
||||
"fact_trade_name": "Firma",
|
||||
"open_dossier": "Öppna {name}",
|
||||
"attn_create": "Skapa förslag",
|
||||
|
||||
Reference in New Issue
Block a user