Skip to content
Open
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
953e03e
Implementing CSV-Import for persons
nobodyzero1 Jul 7, 2025
914dea5
Fix reliability issues reported by SonarQube
nobodyzero1 Jul 7, 2025
4ac06f2
Fix reliability issues reported by SonarQube
nobodyzero1 Jul 8, 2025
e626432
Fix reliability issues reported by SonarQube
nobodyzero1 Jul 8, 2025
1141e9b
Adding changelog
nobodyzero1 Jul 9, 2025
c9fc5ce
Merge branch 'main' into feature/csv-persons-import
nobodyzero1 Jul 9, 2025
144cfb4
Trigger review
nobodyzero1 Jul 9, 2025
abf076b
Trigger review
nobodyzero1 Jul 9, 2025
2f6099e
Minor corrections from code rabbit
nobodyzero1 Jul 9, 2025
a7f6df3
just deleting some comments
nobodyzero1 Jul 9, 2025
d5671cf
correcting general.json
nobodyzero1 Jul 9, 2025
60a2b9e
Merge remote-tracking branch 'origin/main' into feature/csv-persons-i…
nobodyzero1 Aug 1, 2025
d496b97
Merge remote-tracking branch 'origin/main' into feature/csv-persons-i…
nobodyzero1 Sep 2, 2025
bc52747
Continue CSV persons import feature implementation
nobodyzero1 Oct 6, 2025
8628e0e
feat(persons): unify export/import; add group no. and emergency contacts
nobodyzero1 Oct 6, 2025
f35449f
Reducing complexity
nobodyzero1 Oct 9, 2025
07ba6c7
Implement feedback and suggestions for improvement
nobodyzero1 Oct 21, 2025
e6903e9
fixing issues from coderabbitai
nobodyzero1 Oct 21, 2025
af13ea6
fixing further coderabbitai issues
nobodyzero1 Oct 21, 2025
5f619e8
Merge branch 'main' into feature/csv-persons-import
nobodyzero1 Oct 21, 2025
0b48dfa
fixing issues from coderabbitai
nobodyzero1 Oct 22, 2025
bc91edc
fixing issues from coderabbit
nobodyzero1 Oct 23, 2025
b323425
fixing issues from coderabbit
nobodyzero1 Oct 23, 2025
102f567
resolving issue with activating baptized status
nobodyzero1 Oct 23, 2025
aa391ad
fixing coderabbit issues
nobodyzero1 Oct 23, 2025
967762b
allowing null-value for first_report in type-definition bbecause the …
nobodyzero1 Oct 23, 2025
882f8b1
implementing suggestions from coderabbit
nobodyzero1 Oct 23, 2025
e1b95b5
correcting closeOpenHistory according to coderabbit
nobodyzero1 Oct 23, 2025
8efcbaf
Refactor assignment examples logic
nobodyzero1 Oct 23, 2025
73df0c2
resolving examples-issue according to coderabbit
nobodyzero1 Oct 24, 2025
37ebeec
Merge branch 'main' into feature/csv-persons-import
nobodyzero1 Oct 24, 2025
5589bea
Merge branch 'main' into feature/csv-persons-import
nobodyzero1 Nov 11, 2025
c328d7f
feat(ui): Refine link styling and compactify template filling tips
nobodyzero1 Nov 25, 2025
512e71d
updating package for merging
nobodyzero1 Nov 25, 2025
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
404 changes: 205 additions & 199 deletions CHANGELOG.md

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"mui-one-time-password-input": "^5.0.0",
"nanoid": "^5.1.6",
"node-html-parser": "^7.0.1",
"papaparse": "^5.5.3",
"qrcode": "^1.5.4",
"react": "^19.2.0",
"react-dom": "^19.2.0",
Expand Down Expand Up @@ -82,6 +83,7 @@
"@svgx/vite-plugin-react": "^1.0.1",
"@types/file-saver": "^2.0.7",
"@types/mocha": "^10.0.10",
"@types/papaparse": "^5.3.16",
"@types/react": "^19.2.1",
"@types/react-beautiful-dnd": "^13.1.8",
"@types/react-dom": "^19.2.0",
Expand Down
64 changes: 64 additions & 0 deletions src/components/icons/IconImportCSV.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { SvgIcon, SxProps, Theme } from '@mui/material';

type IconProps = {
color?: string;
width?: number;
height?: number;
sx?: SxProps<Theme>;
className?: string;
};

const IconImportCSV = ({
color = '#222222',
width = 24,
height = 24,
sx = {},
className,
}: IconProps) => {
return (
<SvgIcon
className={`organized-icon-import-csv ${className}`}
sx={{ width: `${width}px`, height: `${height}px`, ...sx }}
>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<mask
id="mask0_18280_388529"
style={{ maskType: 'alpha' }}
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="24"
>
<rect width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_18280_388529)">
<path
d="M6.30775 21.5C5.80258 21.5 5.375 21.325 5.025 20.975C4.675 20.625 4.5 20.1974 4.5 19.6923V4.30775C4.5 3.80258 4.675 3.375 5.025 3.025C5.375 2.675 5.80258 2.5 6.30775 2.5H14.25L19.5 7.75V19.6923C19.5 20.1974 19.325 20.625 18.975 20.975C18.625 21.325 18.1974 21.5 17.6923 21.5H6.30775ZM13.5 8.5V4H6.30775C6.23075 4 6.16025 4.03208 6.09625 4.09625C6.03208 4.16025 6 4.23075 6 4.30775V19.6923C6 19.7693 6.03208 19.8398 6.09625 19.9038C6.16025 19.9679 6.23075 20 6.30775 20H17.6923C17.7692 20 17.8398 19.9679 17.9038 19.9038C17.9679 19.8398 18 19.7693 18 19.6923V8.5H13.5Z"
fill={color}
/>
<path
d="M9.8589 15.2274H7.91255C7.69998 15.2274 7.51992 15.1536 7.37238 15.0062C7.22471 14.8587 7.15088 14.6789 7.15088 14.4668V11.8187C7.15088 11.6066 7.22471 11.4268 7.37238 11.2793C7.51992 11.1318 7.69998 11.0581 7.91255 11.0581H9.8589V11.9315H8.13826C8.11003 11.9315 8.08411 11.9432 8.06052 11.9667C8.03705 11.9901 8.02531 12.0159 8.02531 12.0441V14.2414C8.02531 14.2696 8.03705 14.2954 8.06052 14.3188C8.08411 14.3423 8.11003 14.354 8.13826 14.354H9.8589V15.2274Z"
fill={color}
/>
<path
d="M12.313 15.2274H10.3667V14.354H12.0875C12.1157 14.354 12.1416 14.3423 12.1651 14.3188C12.1885 14.2954 12.2003 14.2696 12.2003 14.2414V13.6245C12.2003 13.5963 12.1885 13.5705 12.1651 13.547C12.1416 13.5235 12.1157 13.5117 12.0875 13.5117H11.1284C10.9159 13.5117 10.7358 13.4305 10.5882 13.2682C10.4405 13.1057 10.3667 12.9183 10.3667 12.706V11.8187C10.3667 11.6066 10.4405 11.4268 10.5882 11.2793C10.7358 11.1318 10.9159 11.0581 11.1284 11.0581H13.0747V11.9315H11.3541C11.3258 11.9315 11.3 11.9432 11.2765 11.9667C11.2529 11.9901 11.2411 12.0159 11.2411 12.0441V12.6385C11.2411 12.6667 11.2529 12.6925 11.2765 12.7159C11.3 12.7395 11.3258 12.7513 11.3541 12.7513H12.313C12.5256 12.7513 12.7057 12.8362 12.8534 13.0061C13.0009 13.1761 13.0747 13.3672 13.0747 13.5794V14.4668C13.0747 14.6789 13.0009 14.8587 12.8534 15.0062C12.7057 15.1536 12.5256 15.2274 12.313 15.2274Z"
fill={color}
/>
<path
d="M15.7871 15.2274H14.7997L13.5868 11.0581H14.4753L15.2933 13.8808L16.1114 11.0581H17L15.7871 15.2274Z"
fill={color}
/>
</g>
</svg>
</SvgIcon>
);
};

export default IconImportCSV;
54 changes: 54 additions & 0 deletions src/components/icons/IconUpload.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { SvgIcon, SxProps, Theme } from '@mui/material';
import { useId } from 'react';

type IconProps = {
color?: string;
width?: number;
height?: number;
sx?: SxProps<Theme>;
className?: string;
};

const IconUpload = ({
color = '#222222',
width = 24,
height = 24,
sx = {},
className,
}: IconProps) => {
const maskId = useId();
return (
<SvgIcon
className={`organized-icon-upload ${className}`}
sx={{ width: `${width}px`, height: `${height}px`, ...sx }}
>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<mask
id={maskId}
style={{ maskType: 'alpha' }}
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="24"
height="25"
>
<rect y="0.000488281" width="24" height="24" fill="#D9D9D9" />
</mask>
<g mask={`url(#${maskId})`}>
<path
d="M9 16V10L6.5 12.5L5.4 11.4L12 4.8L18.6 11.4L17.5 12.5L15 10V16H9ZM6 20C5.45 20 4.979 19.804 4.587 19.412C4.195 19.02 3.99934 18.5493 4 18V15H6V18H18V15H20V18C20 18.55 19.804 19.021 19.412 19.413C19.02 19.805 18.5493 20.0007 18 20H6Z"
fill={color}
/>
</g>
</svg>
</SvgIcon>
);
};

export default IconUpload;
1 change: 1 addition & 0 deletions src/components/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,3 +301,4 @@ export { default as IconWeek } from './IconWeek';
export { default as IconWine } from './IconWine';
export { default as IconWork } from './IconWork';
export { default as IconYahoo } from './IconYahoo';
export { default as IconUpload } from './IconUpload';
18 changes: 14 additions & 4 deletions src/definition/person.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
import { AssignmentCode } from './assignment';

export type PrivilegeType = 'elder' | 'ms';
export const ALL_PRIVILEGE_TYPES = ['elder', 'ms'] as const;
export type PrivilegeType = (typeof ALL_PRIVILEGE_TYPES)[number];

export type EnrollmentType = 'AP' | 'FR' | 'FS' | 'FMF';
export const isPrivilegeType = (value: string): value is PrivilegeType => {
return ALL_PRIVILEGE_TYPES.includes(value as PrivilegeType);
};

export const ALL_ENROLLMENT_TYPES = ['AP', 'FR', 'FS', 'FMF'] as const;
export type EnrollmentType = (typeof ALL_ENROLLMENT_TYPES)[number];

export const isEnrollmentType = (value: string): value is EnrollmentType => {
return ALL_ENROLLMENT_TYPES.includes(value as EnrollmentType);
};

export type AssignmentType = {
type: string;
Expand All @@ -24,7 +34,7 @@ export type StatusHistoryType = {
_deleted: boolean;
updatedAt: string;
start_date: string;
end_date: string;
end_date: string | null;
};

type PrivilegeHistoryType = {
Expand Down Expand Up @@ -70,7 +80,7 @@ export type PersonType = {
email: { value: string; updatedAt: string };
address: { value: string; updatedAt: string };
phone: { value: string; updatedAt: string };
first_report?: { value: string; updatedAt: string };
first_report?: { value: string | null; updatedAt: string };
publisher_baptized: {
active: { value: boolean; updatedAt: string };
anointed: { value: boolean; updatedAt: string };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ const useAssignmentGroup = (male: boolean) => {
if (code === AssignmentCode.MM_ExplainingBeliefs) isDisabled = false;
if (code === AssignmentCode.MM_AssistantOnly) isDisabled = false;
}

return isDisabled;
};

Expand Down
109 changes: 109 additions & 0 deletions src/features/persons/assignments/assignmentStructure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { AssignmentCode } from '@definition/assignment';

export interface AssignmentItem {
code: AssignmentCode;
nameKey: string;
borderTop?: boolean;
}

export interface AssignmentSection {
id: string;
headerKey: string;
items: AssignmentItem[];
}

export const ASSIGNMENT_SECTIONS: AssignmentSection[] = [
{
id: 'midweekMeeting',
headerKey: 'tr_midweekMeeting',
items: [
{ code: AssignmentCode.MM_Chairman, nameKey: 'tr_chairman' },
{ code: AssignmentCode.MM_Prayer, nameKey: 'tr_prayer' },
{
code: AssignmentCode.MM_AuxiliaryCounselor,
nameKey: 'tr_auxClassCounselor',
},
],
},
{
id: 'treasuresPart',
headerKey: 'tr_treasuresPart',
items: [
{ code: AssignmentCode.MM_TGWTalk, nameKey: 'tr_tgwTalk' },
{ code: AssignmentCode.MM_TGWGems, nameKey: 'tr_tgwGems' },
{ code: AssignmentCode.MM_BibleReading, nameKey: 'tr_bibleReading' },
],
},
{
id: 'applyFieldMinistryPart',
headerKey: 'tr_applyFieldMinistryPart',
items: [
{ code: AssignmentCode.MM_Discussion, nameKey: 'tr_discussion' },
{
code: AssignmentCode.MM_StartingConversation,
nameKey: 'tr_startingConversation',
},
{ code: AssignmentCode.MM_FollowingUp, nameKey: 'tr_followingUp' },
{
code: AssignmentCode.MM_MakingDisciples,
nameKey: 'tr_makingDisciples',
},
{
code: AssignmentCode.MM_ExplainingBeliefs,
nameKey: 'tr_explainingBeliefs',
},
{ code: AssignmentCode.MM_Talk, nameKey: 'tr_talk' },
{
code: AssignmentCode.MM_AssistantOnly,
nameKey: 'tr_assistantOnly',
borderTop: true,
},
],
},
{
id: 'livingPart',
headerKey: 'tr_livingPart',
items: [
{ code: AssignmentCode.MM_LCPart, nameKey: 'tr_lcPart' },
{
code: AssignmentCode.MM_CBSConductor,
nameKey: 'tr_congregationBibleStudyConductor',
},
{
code: AssignmentCode.MM_CBSReader,
nameKey: 'tr_congregationBibleStudyReader',
},
],
},
{
id: 'weekendMeeting',
headerKey: 'tr_weekendMeeting',
items: [
{ code: AssignmentCode.WM_Chairman, nameKey: 'tr_chairman' },
{ code: AssignmentCode.WM_Prayer, nameKey: 'tr_prayer' },
{ code: AssignmentCode.WM_Speaker, nameKey: 'tr_speaker' },
{
code: AssignmentCode.WM_SpeakerSymposium,
nameKey: 'tr_speakerSymposium',
},
{
code: AssignmentCode.WM_WTStudyConductor,
nameKey: 'tr_watchtowerStudyConductor',
},
{
code: AssignmentCode.WM_WTStudyReader,
nameKey: 'tr_watchtowerStudyReader',
},
],
},
{
id: 'ministry',
headerKey: 'tr_ministry',
items: [
{
code: AssignmentCode.MINISTRY_HOURS_CREDIT,
nameKey: 'tr_reportHoursCredit',
},
],
},
];
26 changes: 26 additions & 0 deletions src/features/persons/assignments/useAssignments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,31 @@ const useAssignments = () => {
];
}, [t]);

const assignmentLookup = useMemo(() => {
const lookup: Record<string, string> = {};

for (const section of assignments) {
const codeHeader = section.header;
for (const item of section.items) {
let codeName: string;

if (typeof item.code === 'string') {
codeName = item.code;
} else {
codeName = AssignmentCode[item.code] ?? String(item.code);
}

lookup[codeName] = codeHeader + '.' + item.name;
}
}

return lookup;
}, [assignments]);

const getAssignmentName = (code: string): string => {
return assignmentLookup[code] ?? code;
};

const handleToggleGroup = async (checked: boolean, id: string) => {
const newPerson = structuredClone(person);

Expand Down Expand Up @@ -307,6 +332,7 @@ const useAssignments = () => {
handleToggleGroup,
male,
disqualified,
getAssignmentName,
};
};

Expand Down
Loading