Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b1b9e7917 |
@@ -1,4 +1,4 @@
|
|||||||
import { Entity, PrimaryColumn, Column, OneToOne, OneToMany, JoinColumn, ManyToOne } from 'typeorm';
|
import { Entity, PrimaryColumn, Column, OneToOne, OneToMany, JoinColumn } from 'typeorm';
|
||||||
import { Users } from './users.entity';
|
import { Users } from './users.entity';
|
||||||
import { AmChildren } from './am_children.entity';
|
import { AmChildren } from './am_children.entity';
|
||||||
|
|
||||||
@@ -37,32 +37,22 @@ export class AssistanteMaternelle {
|
|||||||
@Column({ name: 'ville_residence', length: 100, nullable: true })
|
@Column({ name: 'ville_residence', length: 100, nullable: true })
|
||||||
residence_city?: string;
|
residence_city?: string;
|
||||||
|
|
||||||
@Column({ name: 'date_agrement', type: 'date', nullable: true })
|
@Column( { name: 'date_agrement', type: 'date', nullable: true })
|
||||||
agreement_date?: Date;
|
agreement_date?: Date;
|
||||||
|
|
||||||
@Column({ name: 'annee_experience', type: 'smallint', nullable: true })
|
@Column( { name: 'annee_experience', type: 'smallint', nullable: true })
|
||||||
years_experience?: number;
|
years_experience?: number;
|
||||||
|
|
||||||
@Column({ name: 'specialite', length: 100, nullable: true })
|
@Column( { name: 'specialite', length: 100, nullable: true })
|
||||||
specialty?: string;
|
specialty?: string;
|
||||||
|
|
||||||
@Column({ name: 'place_disponible', type: 'integer', nullable: true })
|
@Column( { name: 'place_disponible', type: 'integer', nullable: true })
|
||||||
places_available?: number;
|
places_available?: number;
|
||||||
|
|
||||||
/** Numéro de dossier (format AAAA-NNNNNN), même valeur que sur utilisateurs (ticket #103) */
|
/** Numéro de dossier (format AAAA-NNNNNN), même valeur que sur utilisateurs (ticket #103) */
|
||||||
@Column({ name: 'numero_dossier', length: 20, nullable: true })
|
@Column({ name: 'numero_dossier', length: 20, nullable: true })
|
||||||
numero_dossier?: string;
|
numero_dossier?: string;
|
||||||
|
|
||||||
/**
|
|
||||||
* Placement AM↔enfant sélectionné sur le TdB AM (couple actif) — ticket #171.
|
|
||||||
*/
|
|
||||||
@Column({ name: 'id_placement_garde_courant', type: 'uuid', nullable: true })
|
|
||||||
id_placement_garde_courant?: string;
|
|
||||||
|
|
||||||
@ManyToOne(() => AmChildren, { nullable: true, onDelete: 'SET NULL' })
|
|
||||||
@JoinColumn({ name: 'id_placement_garde_courant', referencedColumnName: 'id' })
|
|
||||||
placement_garde_courant?: AmChildren;
|
|
||||||
|
|
||||||
@OneToMany(() => AmChildren, (ac) => ac.am)
|
@OneToMany(() => AmChildren, (ac) => ac.am)
|
||||||
amChildren: AmChildren[];
|
amChildren: AmChildren[];
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-29
@@ -10,10 +10,7 @@ describe('AssistantesMaternellesController', () => {
|
|||||||
const authServiceMock = {
|
const authServiceMock = {
|
||||||
createAmDossierStaff: jest.fn(),
|
createAmDossierStaff: jest.fn(),
|
||||||
};
|
};
|
||||||
const amServiceMock = {
|
const amServiceMock = {};
|
||||||
listerCouplesGarde: jest.fn(),
|
|
||||||
definirCoupleGardeCourant: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
@@ -69,29 +66,4 @@ describe('AssistantesMaternellesController', () => {
|
|||||||
);
|
);
|
||||||
expect(res.numero_dossier).toBe('2026-000001');
|
expect(res.numero_dossier).toBe('2026-000001');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('listerCouplesGarde délègue au service (#171)', async () => {
|
|
||||||
amServiceMock.listerCouplesGarde.mockResolvedValue({
|
|
||||||
couples: [],
|
|
||||||
couple_courant_id: null,
|
|
||||||
});
|
|
||||||
const res = await controller.listerCouplesGarde('am-uuid');
|
|
||||||
expect(amServiceMock.listerCouplesGarde).toHaveBeenCalledWith('am-uuid');
|
|
||||||
expect(res.couple_courant_id).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('definirCoupleGardeCourant délègue au service (#171)', async () => {
|
|
||||||
amServiceMock.definirCoupleGardeCourant.mockResolvedValue({
|
|
||||||
couples: [{ id: 'pl-1', courant: true }],
|
|
||||||
couple_courant_id: 'pl-1',
|
|
||||||
});
|
|
||||||
const res = await controller.definirCoupleGardeCourant('am-uuid', {
|
|
||||||
couple_id: 'pl-1',
|
|
||||||
});
|
|
||||||
expect(amServiceMock.definirCoupleGardeCourant).toHaveBeenCalledWith(
|
|
||||||
'am-uuid',
|
|
||||||
'pl-1',
|
|
||||||
);
|
|
||||||
expect(res.couple_courant_id).toBe('pl-1');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import {
|
|||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
Post,
|
Post,
|
||||||
Put,
|
|
||||||
Body,
|
Body,
|
||||||
Patch,
|
Patch,
|
||||||
Param,
|
Param,
|
||||||
@@ -21,8 +20,6 @@ import { UpdateAssistanteDto } from '../user/dto/update_assistante.dto';
|
|||||||
import { UpdateAmFicheAdminDto } from './dto/update-am-fiche-admin.dto';
|
import { UpdateAmFicheAdminDto } from './dto/update-am-fiche-admin.dto';
|
||||||
import { StaffCreateAmDossierDto } from './dto/staff-create-am-dossier.dto';
|
import { StaffCreateAmDossierDto } from './dto/staff-create-am-dossier.dto';
|
||||||
import { StaffCreateAmDossierResponseDto } from './dto/staff-create-am-dossier-response.dto';
|
import { StaffCreateAmDossierResponseDto } from './dto/staff-create-am-dossier-response.dto';
|
||||||
import { CouplesGardeAmResponseDto } from './dto/couples-garde-am.dto';
|
|
||||||
import { DefinirCoupleGardeCourantAmDto } from './dto/definir-couple-garde-courant-am.dto';
|
|
||||||
import { RolesGuard } from 'src/common/guards/roles.guard';
|
import { RolesGuard } from 'src/common/guards/roles.guard';
|
||||||
import { AuthGuard } from 'src/common/guards/auth.guard';
|
import { AuthGuard } from 'src/common/guards/auth.guard';
|
||||||
import { User } from 'src/common/decorators/user.decorator';
|
import { User } from 'src/common/decorators/user.decorator';
|
||||||
@@ -86,36 +83,6 @@ export class AssistantesMaternellesController {
|
|||||||
return mapAmsForApi(ams);
|
return mapAmsForApi(ams);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('me/couples-garde')
|
|
||||||
@Roles(RoleType.ASSISTANTE_MATERNELLE)
|
|
||||||
@ApiOperation({
|
|
||||||
summary: 'Couples de garde de l’AM connectée — ticket #171',
|
|
||||||
description:
|
|
||||||
'Placements actifs (enfant + parents) pour peupler le bandeau couple TdB AM, ' +
|
|
||||||
'avec indication du couple courant.',
|
|
||||||
})
|
|
||||||
@ApiResponse({ status: 200, type: CouplesGardeAmResponseDto })
|
|
||||||
listerCouplesGarde(
|
|
||||||
@User('id') userId: string,
|
|
||||||
): Promise<CouplesGardeAmResponseDto> {
|
|
||||||
return this.assistantesMaternellesService.listerCouplesGarde(userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Put('me/couples-garde/courant')
|
|
||||||
@Roles(RoleType.ASSISTANTE_MATERNELLE)
|
|
||||||
@ApiOperation({ summary: 'Définir le couple de garde courant (AM) — ticket #171' })
|
|
||||||
@ApiBody({ type: DefinirCoupleGardeCourantAmDto })
|
|
||||||
@ApiResponse({ status: 200, type: CouplesGardeAmResponseDto })
|
|
||||||
definirCoupleGardeCourant(
|
|
||||||
@User('id') userId: string,
|
|
||||||
@Body() dto: DefinirCoupleGardeCourantAmDto,
|
|
||||||
): Promise<CouplesGardeAmResponseDto> {
|
|
||||||
return this.assistantesMaternellesService.definirCoupleGardeCourant(
|
|
||||||
userId,
|
|
||||||
dto.couple_id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Roles(RoleType.SUPER_ADMIN, RoleType.GESTIONNAIRE, RoleType.ADMINISTRATEUR)
|
@Roles(RoleType.SUPER_ADMIN, RoleType.GESTIONNAIRE, RoleType.ADMINISTRATEUR)
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@ApiParam({ name: 'id', description: "UUID de la nounou" })
|
@ApiParam({ name: 'id', description: "UUID de la nounou" })
|
||||||
|
|||||||
@@ -4,21 +4,13 @@ import { AssistantesMaternellesController } from './assistantes_maternelles.cont
|
|||||||
import { AssistanteMaternelle } from 'src/entities/assistantes_maternelles.entity';
|
import { AssistanteMaternelle } from 'src/entities/assistantes_maternelles.entity';
|
||||||
import { AmChildren } from 'src/entities/am_children.entity';
|
import { AmChildren } from 'src/entities/am_children.entity';
|
||||||
import { Children } from 'src/entities/children.entity';
|
import { Children } from 'src/entities/children.entity';
|
||||||
import { ParentsChildren } from 'src/entities/parents_children.entity';
|
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { Users } from 'src/entities/users.entity';
|
import { Users } from 'src/entities/users.entity';
|
||||||
import { AuthModule } from '../auth/auth.module';
|
import { AuthModule } from '../auth/auth.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [TypeOrmModule.forFeature([AssistanteMaternelle, AmChildren, Children, Users]),
|
||||||
TypeOrmModule.forFeature([
|
AuthModule
|
||||||
AssistanteMaternelle,
|
|
||||||
AmChildren,
|
|
||||||
Children,
|
|
||||||
Users,
|
|
||||||
ParentsChildren,
|
|
||||||
]),
|
|
||||||
AuthModule,
|
|
||||||
],
|
],
|
||||||
controllers: [AssistantesMaternellesController],
|
controllers: [AssistantesMaternellesController],
|
||||||
providers: [AssistantesMaternellesService],
|
providers: [AssistantesMaternellesService],
|
||||||
@@ -27,4 +19,4 @@ import { AuthModule } from '../auth/auth.module';
|
|||||||
TypeOrmModule,
|
TypeOrmModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AssistantesMaternellesModule {}
|
export class AssistantesMaternellesModule { }
|
||||||
|
|||||||
+4
-136
@@ -1,150 +1,18 @@
|
|||||||
import { NotFoundException } from '@nestjs/common';
|
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
|
||||||
import { AssistantesMaternellesService } from './assistantes_maternelles.service';
|
import { AssistantesMaternellesService } from './assistantes_maternelles.service';
|
||||||
import { AssistanteMaternelle } from 'src/entities/assistantes_maternelles.entity';
|
|
||||||
import { Users } from 'src/entities/users.entity';
|
|
||||||
import { AmChildren } from 'src/entities/am_children.entity';
|
|
||||||
import { Children } from 'src/entities/children.entity';
|
|
||||||
import { ParentsChildren } from 'src/entities/parents_children.entity';
|
|
||||||
|
|
||||||
describe('AssistantesMaternellesService — couples de garde (#171)', () => {
|
describe('AssistantesMaternellesService', () => {
|
||||||
let service: AssistantesMaternellesService;
|
let service: AssistantesMaternellesService;
|
||||||
|
|
||||||
const amRepo = { findOne: jest.fn(), update: jest.fn() };
|
|
||||||
const usersRepo = {};
|
|
||||||
const amChildrenRepo = { find: jest.fn(), findOne: jest.fn() };
|
|
||||||
const childrenRepo = {};
|
|
||||||
const parentsChildrenRepo = { find: jest.fn() };
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
jest.clearAllMocks();
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [
|
providers: [AssistantesMaternellesService],
|
||||||
AssistantesMaternellesService,
|
|
||||||
{ provide: getRepositoryToken(AssistanteMaternelle), useValue: amRepo },
|
|
||||||
{ provide: getRepositoryToken(Users), useValue: usersRepo },
|
|
||||||
{ provide: getRepositoryToken(AmChildren), useValue: amChildrenRepo },
|
|
||||||
{ provide: getRepositoryToken(Children), useValue: childrenRepo },
|
|
||||||
{
|
|
||||||
provide: getRepositoryToken(ParentsChildren),
|
|
||||||
useValue: parentsChildrenRepo,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}).compile();
|
}).compile();
|
||||||
service = module.get(AssistantesMaternellesService);
|
|
||||||
|
service = module.get<AssistantesMaternellesService>(AssistantesMaternellesService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
expect(service).toBeDefined();
|
expect(service).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('listerCouplesGarde', () => {
|
|
||||||
it('retourne vide si aucun placement', async () => {
|
|
||||||
amRepo.findOne.mockResolvedValue({
|
|
||||||
user_id: 'am-1',
|
|
||||||
id_placement_garde_courant: null,
|
|
||||||
});
|
|
||||||
amChildrenRepo.find.mockResolvedValue([]);
|
|
||||||
const res = await service.listerCouplesGarde('am-1');
|
|
||||||
expect(res).toEqual({ couples: [], couple_courant_id: null });
|
|
||||||
expect(parentsChildrenRepo.find).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('mappe placements + parents et marque le courant', async () => {
|
|
||||||
amRepo.findOne.mockResolvedValue({
|
|
||||||
user_id: 'am-1',
|
|
||||||
id_placement_garde_courant: 'pl-2',
|
|
||||||
});
|
|
||||||
amChildrenRepo.find.mockResolvedValue([
|
|
||||||
{
|
|
||||||
id: 'pl-1',
|
|
||||||
enfantId: 'e1',
|
|
||||||
child: { id: 'e1', first_name: 'Léa', last_name: 'M', photo_url: null },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'pl-2',
|
|
||||||
enfantId: 'e2',
|
|
||||||
child: { id: 'e2', first_name: 'Emma', last_name: 'M', photo_url: null },
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
parentsChildrenRepo.find.mockResolvedValue([
|
|
||||||
{
|
|
||||||
parentId: 'p1',
|
|
||||||
enfantId: 'e1',
|
|
||||||
parent: { user: { prenom: 'Claire', nom: 'Martin', photo_url: null } },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
parentId: 'p2',
|
|
||||||
enfantId: 'e1',
|
|
||||||
parent: { user: { prenom: 'Thomas', nom: 'Martin', photo_url: null } },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
parentId: 'p1',
|
|
||||||
enfantId: 'e2',
|
|
||||||
parent: { user: { prenom: 'Claire', nom: 'Martin', photo_url: null } },
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const res = await service.listerCouplesGarde('am-1');
|
|
||||||
expect(res.couples).toHaveLength(2);
|
|
||||||
expect(res.couple_courant_id).toBe('pl-2');
|
|
||||||
expect(res.couples[1].courant).toBe(true);
|
|
||||||
expect(res.couples[0].parents).toHaveLength(2);
|
|
||||||
expect(res.couples[0].parents[0].prenom).toBe('Claire');
|
|
||||||
expect(res.couples[0].enfant.prenom).toBe('Léa');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('404 si AM inconnue', async () => {
|
|
||||||
amRepo.findOne.mockResolvedValue(null);
|
|
||||||
await expect(service.listerCouplesGarde('x')).rejects.toBeInstanceOf(
|
|
||||||
NotFoundException,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('definirCoupleGardeCourant', () => {
|
|
||||||
it('persiste si le placement appartient à l’AM', async () => {
|
|
||||||
amRepo.findOne.mockResolvedValue({
|
|
||||||
user_id: 'am-1',
|
|
||||||
id_placement_garde_courant: null,
|
|
||||||
});
|
|
||||||
amChildrenRepo.findOne.mockResolvedValue({
|
|
||||||
id: 'pl-1',
|
|
||||||
amId: 'am-1',
|
|
||||||
enfantId: 'e1',
|
|
||||||
});
|
|
||||||
amRepo.update.mockResolvedValue({ affected: 1 });
|
|
||||||
amChildrenRepo.find.mockResolvedValue([
|
|
||||||
{
|
|
||||||
id: 'pl-1',
|
|
||||||
enfantId: 'e1',
|
|
||||||
child: { id: 'e1', first_name: 'Léa', last_name: null, photo_url: null },
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
parentsChildrenRepo.find.mockResolvedValue([
|
|
||||||
{
|
|
||||||
parentId: 'p1',
|
|
||||||
enfantId: 'e1',
|
|
||||||
parent: { user: { prenom: 'Claire', nom: 'M', photo_url: null } },
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const res = await service.definirCoupleGardeCourant('am-1', 'pl-1');
|
|
||||||
expect(amRepo.update).toHaveBeenCalledWith(
|
|
||||||
{ user_id: 'am-1' },
|
|
||||||
{ id_placement_garde_courant: 'pl-1' },
|
|
||||||
);
|
|
||||||
expect(res.couple_courant_id).toBe('pl-1');
|
|
||||||
expect(res.couples[0].courant).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('404 si placement d’une autre AM', async () => {
|
|
||||||
amRepo.findOne.mockResolvedValue({ user_id: 'am-1' });
|
|
||||||
amChildrenRepo.findOne.mockResolvedValue(null);
|
|
||||||
await expect(
|
|
||||||
service.definirCoupleGardeCourant('am-1', 'pl-x'),
|
|
||||||
).rejects.toBeInstanceOf(NotFoundException);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,16 +5,14 @@ import {
|
|||||||
NotFoundException,
|
NotFoundException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { In, IsNull, Repository } from 'typeorm';
|
import { IsNull, Repository } from 'typeorm';
|
||||||
import { RoleType, Users } from 'src/entities/users.entity';
|
import { RoleType, Users } from 'src/entities/users.entity';
|
||||||
import { AssistanteMaternelle } from 'src/entities/assistantes_maternelles.entity';
|
import { AssistanteMaternelle } from 'src/entities/assistantes_maternelles.entity';
|
||||||
import { AmChildren } from 'src/entities/am_children.entity';
|
import { AmChildren } from 'src/entities/am_children.entity';
|
||||||
import { Children, StatutEnfantType } from 'src/entities/children.entity';
|
import { Children, StatutEnfantType } from 'src/entities/children.entity';
|
||||||
import { ParentsChildren } from 'src/entities/parents_children.entity';
|
|
||||||
import { CreateAssistanteDto } from '../user/dto/create_assistante.dto';
|
import { CreateAssistanteDto } from '../user/dto/create_assistante.dto';
|
||||||
import { UpdateAssistanteDto } from '../user/dto/update_assistante.dto';
|
import { UpdateAssistanteDto } from '../user/dto/update_assistante.dto';
|
||||||
import { UpdateAmFicheAdminDto } from './dto/update-am-fiche-admin.dto';
|
import { UpdateAmFicheAdminDto } from './dto/update-am-fiche-admin.dto';
|
||||||
import { CouplesGardeAmResponseDto } from './dto/couples-garde-am.dto';
|
|
||||||
import { validateNir } from 'src/common/utils/nir.util';
|
import { validateNir } from 'src/common/utils/nir.util';
|
||||||
|
|
||||||
const AM_CHILDREN_RELATIONS = ['user', 'amChildren', 'amChildren.child'] as const;
|
const AM_CHILDREN_RELATIONS = ['user', 'amChildren', 'amChildren.child'] as const;
|
||||||
@@ -30,8 +28,6 @@ export class AssistantesMaternellesService {
|
|||||||
private readonly amChildrenRepository: Repository<AmChildren>,
|
private readonly amChildrenRepository: Repository<AmChildren>,
|
||||||
@InjectRepository(Children)
|
@InjectRepository(Children)
|
||||||
private readonly childrenRepository: Repository<Children>,
|
private readonly childrenRepository: Repository<Children>,
|
||||||
@InjectRepository(ParentsChildren)
|
|
||||||
private readonly parentsChildrenRepository: Repository<ParentsChildren>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async create(dto: CreateAssistanteDto): Promise<AssistanteMaternelle> {
|
async create(dto: CreateAssistanteDto): Promise<AssistanteMaternelle> {
|
||||||
@@ -268,107 +264,4 @@ export class AssistantesMaternellesService {
|
|||||||
await this.assistantesMaternelleRepository.delete(id);
|
await this.assistantesMaternelleRepository.delete(id);
|
||||||
return { message: 'Assistante maternelle supprimée' };
|
return { message: 'Assistante maternelle supprimée' };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Liste les couples de garde (enfant ↔ parents) de l’AM connectée — ticket #171.
|
|
||||||
* Un couple = un placement actif dans enfants_assistantes_maternelles pour cette AM.
|
|
||||||
*/
|
|
||||||
async listerCouplesGarde(amUserId: string): Promise<CouplesGardeAmResponseDto> {
|
|
||||||
const am = await this.assistantesMaternelleRepository.findOne({
|
|
||||||
where: { user_id: amUserId },
|
|
||||||
});
|
|
||||||
if (!am) {
|
|
||||||
throw new NotFoundException('Assistante maternelle introuvable');
|
|
||||||
}
|
|
||||||
|
|
||||||
const placements = await this.amChildrenRepository.find({
|
|
||||||
where: { amId: amUserId, date_fin: IsNull() },
|
|
||||||
relations: ['child'],
|
|
||||||
order: { date_debut: 'ASC' },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (placements.length === 0) {
|
|
||||||
return { couples: [], couple_courant_id: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
const enfantIds = placements.map((p) => p.enfantId);
|
|
||||||
const liens = await this.parentsChildrenRepository.find({
|
|
||||||
where: { enfantId: In(enfantIds) },
|
|
||||||
relations: ['parent', 'parent.user'],
|
|
||||||
});
|
|
||||||
const parentsByEnfant = new Map<
|
|
||||||
string,
|
|
||||||
{ id: string; prenom: string | null; nom: string | null; photo_url: string | null }[]
|
|
||||||
>();
|
|
||||||
for (const l of liens) {
|
|
||||||
const list = parentsByEnfant.get(l.enfantId) ?? [];
|
|
||||||
const u = l.parent?.user;
|
|
||||||
list.push({
|
|
||||||
id: l.parentId,
|
|
||||||
prenom: u?.prenom ?? null,
|
|
||||||
nom: u?.nom ?? null,
|
|
||||||
photo_url: u?.photo_url ?? null,
|
|
||||||
});
|
|
||||||
parentsByEnfant.set(l.enfantId, list);
|
|
||||||
}
|
|
||||||
|
|
||||||
let idCourant = am.id_placement_garde_courant ?? null;
|
|
||||||
const idsValides = new Set(placements.map((p) => p.id));
|
|
||||||
if (idCourant && !idsValides.has(idCourant)) {
|
|
||||||
idCourant = null;
|
|
||||||
await this.assistantesMaternelleRepository.update(
|
|
||||||
{ user_id: amUserId },
|
|
||||||
{ id_placement_garde_courant: () => 'NULL' },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!idCourant && placements.length === 1) {
|
|
||||||
idCourant = placements[0].id;
|
|
||||||
}
|
|
||||||
|
|
||||||
const couples = placements.map((p) => ({
|
|
||||||
id: p.id,
|
|
||||||
enfant: {
|
|
||||||
id: p.child.id,
|
|
||||||
prenom: p.child.first_name ?? null,
|
|
||||||
nom: p.child.last_name ?? null,
|
|
||||||
photo_url: p.child.photo_url ?? null,
|
|
||||||
},
|
|
||||||
parents: parentsByEnfant.get(p.enfantId) ?? [],
|
|
||||||
courant: idCourant != null && p.id === idCourant,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
|
||||||
couples,
|
|
||||||
couple_courant_id: idCourant,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Persiste le couple de garde actif pour l’AM — ticket #171.
|
|
||||||
*/
|
|
||||||
async definirCoupleGardeCourant(
|
|
||||||
amUserId: string,
|
|
||||||
coupleId: string,
|
|
||||||
): Promise<CouplesGardeAmResponseDto> {
|
|
||||||
const am = await this.assistantesMaternelleRepository.findOne({
|
|
||||||
where: { user_id: amUserId },
|
|
||||||
});
|
|
||||||
if (!am) {
|
|
||||||
throw new NotFoundException('Assistante maternelle introuvable');
|
|
||||||
}
|
|
||||||
|
|
||||||
const placement = await this.amChildrenRepository.findOne({
|
|
||||||
where: { id: coupleId, amId: amUserId, date_fin: IsNull() },
|
|
||||||
});
|
|
||||||
if (!placement) {
|
|
||||||
throw new NotFoundException('Couple de garde introuvable ou inactif pour cette AM');
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.assistantesMaternelleRepository.update(
|
|
||||||
{ user_id: amUserId },
|
|
||||||
{ id_placement_garde_courant: coupleId },
|
|
||||||
);
|
|
||||||
|
|
||||||
return this.listerCouplesGarde(amUserId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
||||||
|
|
||||||
/** Identité minimale enfant — ticket #171 (miroir #168) */
|
|
||||||
export class CoupleGardeAmEnfantDto {
|
|
||||||
@ApiProperty({ format: 'uuid' })
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
prenom?: string | null;
|
|
||||||
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
nom?: string | null;
|
|
||||||
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
photo_url?: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Identité minimale parent pour bandeau AM — ticket #171 */
|
|
||||||
export class CoupleGardeAmParentDto {
|
|
||||||
@ApiProperty({ format: 'uuid', description: 'UUID utilisateur du parent' })
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
prenom?: string | null;
|
|
||||||
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
nom?: string | null;
|
|
||||||
|
|
||||||
@ApiPropertyOptional()
|
|
||||||
photo_url?: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Un couple côté AM = placement actif enfant ↔ foyer parents.
|
|
||||||
* `id` = enfants_assistantes_maternelles.id (même clé que #168).
|
|
||||||
*/
|
|
||||||
export class CoupleGardeAmDto {
|
|
||||||
@ApiProperty({
|
|
||||||
format: 'uuid',
|
|
||||||
description: 'Id du placement (enfants_assistantes_maternelles.id)',
|
|
||||||
})
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
@ApiProperty({ type: CoupleGardeAmEnfantDto })
|
|
||||||
enfant: CoupleGardeAmEnfantDto;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
type: [CoupleGardeAmParentDto],
|
|
||||||
description: 'Parents rattachés à l’enfant (0–2 typiquement)',
|
|
||||||
})
|
|
||||||
parents: CoupleGardeAmParentDto[];
|
|
||||||
|
|
||||||
@ApiProperty({ description: 'True si c’est le couple actuellement sélectionné' })
|
|
||||||
courant: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CouplesGardeAmResponseDto {
|
|
||||||
@ApiProperty({ type: [CoupleGardeAmDto] })
|
|
||||||
couples: CoupleGardeAmDto[];
|
|
||||||
|
|
||||||
@ApiPropertyOptional({
|
|
||||||
format: 'uuid',
|
|
||||||
nullable: true,
|
|
||||||
description: 'Id du couple courant (null si aucun)',
|
|
||||||
})
|
|
||||||
couple_courant_id: string | null;
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
|
||||||
import { IsNotEmpty, IsUUID } from 'class-validator';
|
|
||||||
|
|
||||||
/** Corps PUT couple de garde courant AM — ticket #171 */
|
|
||||||
export class DefinirCoupleGardeCourantAmDto {
|
|
||||||
@ApiProperty({
|
|
||||||
format: 'uuid',
|
|
||||||
description: 'Id du placement (enfants_assistantes_maternelles.id) à sélectionner',
|
|
||||||
})
|
|
||||||
@IsUUID()
|
|
||||||
@IsNotEmpty()
|
|
||||||
couple_id: string;
|
|
||||||
}
|
|
||||||
+1
-14
@@ -143,10 +143,7 @@ CREATE TABLE assistantes_maternelles (
|
|||||||
annee_experience SMALLINT,
|
annee_experience SMALLINT,
|
||||||
specialite VARCHAR(100),
|
specialite VARCHAR(100),
|
||||||
place_disponible INT,
|
place_disponible INT,
|
||||||
numero_dossier VARCHAR(20),
|
numero_dossier VARCHAR(20)
|
||||||
-- Préférence couple de garde actif (TdB quotidien AM) — ticket #171
|
|
||||||
-- FK ajoutée après création de enfants_assistantes_maternelles (voir ALTER plus bas)
|
|
||||||
id_placement_garde_courant UUID
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX idx_assistantes_maternelles_numero_dossier
|
CREATE INDEX idx_assistantes_maternelles_numero_dossier
|
||||||
@@ -224,16 +221,6 @@ CREATE INDEX idx_parents_placement_garde_courant
|
|||||||
ON parents(id_placement_garde_courant)
|
ON parents(id_placement_garde_courant)
|
||||||
WHERE id_placement_garde_courant IS NOT NULL;
|
WHERE id_placement_garde_courant IS NOT NULL;
|
||||||
|
|
||||||
-- FK couple courant AM → placement (#171)
|
|
||||||
ALTER TABLE assistantes_maternelles
|
|
||||||
ADD CONSTRAINT fk_am_placement_garde_courant
|
|
||||||
FOREIGN KEY (id_placement_garde_courant)
|
|
||||||
REFERENCES enfants_assistantes_maternelles(id) ON DELETE SET NULL;
|
|
||||||
|
|
||||||
CREATE INDEX idx_am_placement_garde_courant
|
|
||||||
ON assistantes_maternelles(id_placement_garde_courant)
|
|
||||||
WHERE id_placement_garde_courant IS NOT NULL;
|
|
||||||
|
|
||||||
-- ==========================================================
|
-- ==========================================================
|
||||||
-- Table : dossier_famille (inscription parent — ticket #119)
|
-- Table : dossier_famille (inscription parent — ticket #119)
|
||||||
-- ==========================================================
|
-- ==========================================================
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
-- Ticket #171 — Couple de garde courant (préférence AM)
|
|
||||||
-- Idempotent : safe à rejouer.
|
|
||||||
|
|
||||||
ALTER TABLE assistantes_maternelles
|
|
||||||
ADD COLUMN IF NOT EXISTS id_placement_garde_courant UUID
|
|
||||||
REFERENCES enfants_assistantes_maternelles(id) ON DELETE SET NULL;
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_am_placement_garde_courant
|
|
||||||
ON assistantes_maternelles(id_placement_garde_courant)
|
|
||||||
WHERE id_placement_garde_courant IS NOT NULL;
|
|
||||||
@@ -39,15 +39,13 @@ class CoupleMembre {
|
|||||||
class CoupleGarde {
|
class CoupleGarde {
|
||||||
final String id;
|
final String id;
|
||||||
final CoupleMembre enfant;
|
final CoupleMembre enfant;
|
||||||
final CoupleMembre? am;
|
final CoupleMembre am;
|
||||||
final List<CoupleMembre> parents;
|
|
||||||
final bool courant;
|
final bool courant;
|
||||||
|
|
||||||
const CoupleGarde({
|
const CoupleGarde({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.enfant,
|
required this.enfant,
|
||||||
this.am,
|
required this.am,
|
||||||
this.parents = const [],
|
|
||||||
this.courant = false,
|
this.courant = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -57,15 +55,9 @@ class CoupleGarde {
|
|||||||
enfant: CoupleMembre.fromJson(
|
enfant: CoupleMembre.fromJson(
|
||||||
Map<String, dynamic>.from(json['enfant'] ?? const {}),
|
Map<String, dynamic>.from(json['enfant'] ?? const {}),
|
||||||
),
|
),
|
||||||
am: json['am'] != null
|
am: CoupleMembre.fromJson(
|
||||||
? CoupleMembre.fromJson(Map<String, dynamic>.from(json['am']))
|
Map<String, dynamic>.from(json['am'] ?? const {}),
|
||||||
: null,
|
),
|
||||||
parents: json['parents'] != null
|
|
||||||
? (json['parents'] as List)
|
|
||||||
.whereType<Map>()
|
|
||||||
.map((e) => CoupleMembre.fromJson(Map<String, dynamic>.from(e)))
|
|
||||||
.toList()
|
|
||||||
: const [],
|
|
||||||
courant: json['courant'] == true,
|
courant: json['courant'] == true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -75,7 +67,6 @@ class CoupleGarde {
|
|||||||
id: id,
|
id: id,
|
||||||
enfant: enfant,
|
enfant: enfant,
|
||||||
am: am,
|
am: am,
|
||||||
parents: parents,
|
|
||||||
courant: courant ?? this.courant,
|
courant: courant ?? this.courant,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:p_tits_pas/models/couple_garde.dart';
|
|
||||||
import 'package:p_tits_pas/models/user.dart';
|
import 'package:p_tits_pas/models/user.dart';
|
||||||
import 'package:p_tits_pas/services/auth_service.dart';
|
import 'package:p_tits_pas/services/auth_service.dart';
|
||||||
import 'package:p_tits_pas/services/couple_garde_service.dart';
|
|
||||||
import 'package:p_tits_pas/widgets/quotidien/couple_selector_bandeau.dart';
|
|
||||||
import 'package:p_tits_pas/widgets/quotidien/quotidien_shell.dart';
|
import 'package:p_tits_pas/widgets/quotidien/quotidien_shell.dart';
|
||||||
import 'package:p_tits_pas/widgets/quotidien/quotidien_theme.dart';
|
import 'package:p_tits_pas/widgets/quotidien/quotidien_theme.dart';
|
||||||
import 'package:p_tits_pas/screens/home/parent_screen/agenda_absences_stub.dart';
|
|
||||||
|
|
||||||
/// Dashboard assistante maternelle – coquille 3 colonnes quotidien (#169).
|
/// Dashboard assistante maternelle – coquille 3 colonnes quotidien (#169).
|
||||||
/// Colonne gauche : sélecteur de couple enfant–parent(s) (#170).
|
/// Colonne gauche : sélecteur de couple enfant–parent(s) (à venir #170).
|
||||||
/// Métier cartes / blog / messagerie : tickets C/D/E.
|
/// Métier cartes / blog / messagerie : tickets C/D/E.
|
||||||
class AmDashboardScreen extends StatefulWidget {
|
class AmDashboardScreen extends StatefulWidget {
|
||||||
const AmDashboardScreen({super.key});
|
const AmDashboardScreen({super.key});
|
||||||
@@ -22,16 +18,10 @@ class _AmDashboardScreenState extends State<AmDashboardScreen> {
|
|||||||
QuotidienNavSection _section = QuotidienNavSection.liaison;
|
QuotidienNavSection _section = QuotidienNavSection.liaison;
|
||||||
AppUser? _user;
|
AppUser? _user;
|
||||||
|
|
||||||
List<CoupleGarde> _couples = const [];
|
|
||||||
String? _selectedCoupleId;
|
|
||||||
bool _couplesLoading = true;
|
|
||||||
String? _couplesError;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_loadUser();
|
_loadUser();
|
||||||
_loadCouples();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadUser() async {
|
Future<void> _loadUser() async {
|
||||||
@@ -39,51 +29,6 @@ class _AmDashboardScreenState extends State<AmDashboardScreen> {
|
|||||||
if (mounted) setState(() => _user = user);
|
if (mounted) setState(() => _user = user);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadCouples() async {
|
|
||||||
setState(() {
|
|
||||||
_couplesLoading = true;
|
|
||||||
_couplesError = null;
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
final res = await CoupleGardeService.getAmCouplesGarde();
|
|
||||||
if (!mounted) return;
|
|
||||||
setState(() {
|
|
||||||
_couples = res.couples;
|
|
||||||
_selectedCoupleId = res.coupleCourant?.id;
|
|
||||||
_couplesLoading = false;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
if (!mounted) return;
|
|
||||||
setState(() {
|
|
||||||
_couplesError = e.toString().replaceFirst('Exception: ', '');
|
|
||||||
_couplesLoading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _selectCouple(CoupleGarde couple) async {
|
|
||||||
if (couple.id == _selectedCoupleId) return;
|
|
||||||
setState(() => _selectedCoupleId = couple.id);
|
|
||||||
try {
|
|
||||||
final res = await CoupleGardeService.definirAmCoupleCourant(couple.id);
|
|
||||||
if (!mounted) return;
|
|
||||||
setState(() {
|
|
||||||
_couples = res.couples;
|
|
||||||
_selectedCoupleId = res.coupleCourant?.id ?? couple.id;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
if (!mounted) return;
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Text(
|
|
||||||
"Impossible de changer d'enfant : "
|
|
||||||
'${e.toString().replaceFirst('Exception: ', '')}',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String get _displayName {
|
String get _displayName {
|
||||||
final n = _user?.fullName.trim() ?? '';
|
final n = _user?.fullName.trim() ?? '';
|
||||||
if (n.isNotEmpty) return n;
|
if (n.isNotEmpty) return n;
|
||||||
@@ -109,13 +54,11 @@ class _AmDashboardScreenState extends State<AmDashboardScreen> {
|
|||||||
onSettingsTap: () => _soon('Paramètres'),
|
onSettingsTap: () => _soon('Paramètres'),
|
||||||
// L'AM n'a pas de bouton "Recherche AM" dans le bandeau
|
// L'AM n'a pas de bouton "Recherche AM" dans le bandeau
|
||||||
onSearchAmTap: null,
|
onSearchAmTap: null,
|
||||||
leftColumn: _LeftColumn(
|
leftColumn: const QuotidienColumnPlaceholder(
|
||||||
couples: _couples,
|
title: 'Cartes',
|
||||||
selectedCoupleId: _selectedCoupleId,
|
subtitle:
|
||||||
loading: _couplesLoading,
|
'Couple enfant–parent(s) et flux de cartes\n(à brancher — tickets #170 / #174).',
|
||||||
error: _couplesError,
|
icon: Icons.style_outlined,
|
||||||
onRetry: _loadCouples,
|
|
||||||
onCoupleSelected: _selectCouple,
|
|
||||||
),
|
),
|
||||||
centerColumn: const QuotidienColumnPlaceholder(
|
centerColumn: const QuotidienColumnPlaceholder(
|
||||||
title: 'Blog',
|
title: 'Blog',
|
||||||
@@ -129,7 +72,10 @@ class _AmDashboardScreenState extends State<AmDashboardScreen> {
|
|||||||
'Mess. Parents · Mess. RPE\n(à brancher — ticket #185).',
|
'Mess. Parents · Mess. RPE\n(à brancher — ticket #185).',
|
||||||
icon: Icons.chat_bubble_outline,
|
icon: Icons.chat_bubble_outline,
|
||||||
),
|
),
|
||||||
agendaBody: AgendaAbsencesStub(placementId: _selectedCoupleId),
|
agendaBody: const QuotidienStubPage(
|
||||||
|
title: 'Agenda',
|
||||||
|
message: 'Agenda — contenu à venir (stub #187).',
|
||||||
|
),
|
||||||
contratBody: const QuotidienStubPage(
|
contratBody: const QuotidienStubPage(
|
||||||
title: 'Contrat',
|
title: 'Contrat',
|
||||||
message: 'Contrat — contenu à venir (stub #187).',
|
message: 'Contrat — contenu à venir (stub #187).',
|
||||||
@@ -138,51 +84,3 @@ class _AmDashboardScreenState extends State<AmDashboardScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LeftColumn extends StatelessWidget {
|
|
||||||
final List<CoupleGarde> couples;
|
|
||||||
final String? selectedCoupleId;
|
|
||||||
final bool loading;
|
|
||||||
final String? error;
|
|
||||||
final VoidCallback onRetry;
|
|
||||||
final ValueChanged<CoupleGarde> onCoupleSelected;
|
|
||||||
|
|
||||||
const _LeftColumn({
|
|
||||||
required this.couples,
|
|
||||||
required this.selectedCoupleId,
|
|
||||||
required this.loading,
|
|
||||||
required this.error,
|
|
||||||
required this.onRetry,
|
|
||||||
required this.onCoupleSelected,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(14),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
CoupleSelectorBandeau(
|
|
||||||
mode: CoupleBandeauMode.assistanteMaternelle,
|
|
||||||
couples: couples,
|
|
||||||
selectedCoupleId: selectedCoupleId,
|
|
||||||
loading: loading,
|
|
||||||
errorMessage: error,
|
|
||||||
onRetry: onRetry,
|
|
||||||
onCoupleSelected: onCoupleSelected,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 14),
|
|
||||||
const Expanded(
|
|
||||||
child: QuotidienColumnPlaceholder(
|
|
||||||
title: 'Cartes',
|
|
||||||
subtitle:
|
|
||||||
'Absences, congés AM, sorties à valider\n(à brancher — ticket #174).',
|
|
||||||
icon: Icons.style_outlined,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -67,11 +67,7 @@ class ApiConfig {
|
|||||||
static const String parentsCoupleGardeCourant =
|
static const String parentsCoupleGardeCourant =
|
||||||
'/parents/me/couples-garde/courant';
|
'/parents/me/couples-garde/courant';
|
||||||
static const String assistantesMaternelles = '/assistantes-maternelles';
|
static const String assistantesMaternelles = '/assistantes-maternelles';
|
||||||
/// Couples de garde de l'AM connectée (#170 / #171).
|
/// Création dossier AM actif par le staff (#156) — body type register AM.
|
||||||
static const String assistantesMaternellesCouplesGarde =
|
|
||||||
'/assistantes-maternelles/me/couples-garde';
|
|
||||||
static const String assistantesMaternellesCoupleGardeCourant =
|
|
||||||
'/assistantes-maternelles/me/couples-garde/courant';
|
|
||||||
static const String assistantesMaternellesDossier =
|
static const String assistantesMaternellesDossier =
|
||||||
'/assistantes-maternelles/dossier';
|
'/assistantes-maternelles/dossier';
|
||||||
static const String enfants = '/enfants';
|
static const String enfants = '/enfants';
|
||||||
|
|||||||
@@ -73,45 +73,4 @@ class CoupleGardeService {
|
|||||||
Map<String, dynamic>.from(decoded as Map),
|
Map<String, dynamic>.from(decoded as Map),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Liste les couples de garde et le couple courant de l'AM connectée.
|
|
||||||
static Future<CouplesGardeResponse> getAmCouplesGarde() async {
|
|
||||||
final response = await http.get(
|
|
||||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.assistantesMaternellesCouplesGarde}'),
|
|
||||||
headers: await _headers(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
|
||||||
throw Exception(
|
|
||||||
_extractError(response.body, 'Erreur chargement des couples de garde (AM)'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final decoded = jsonDecode(response.body);
|
|
||||||
return CouplesGardeResponse.fromJson(
|
|
||||||
Map<String, dynamic>.from(decoded as Map),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Persiste le couple courant (AM) et renvoie la liste à jour.
|
|
||||||
static Future<CouplesGardeResponse> definirAmCoupleCourant(
|
|
||||||
String coupleId,
|
|
||||||
) async {
|
|
||||||
final response = await http.put(
|
|
||||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.assistantesMaternellesCoupleGardeCourant}'),
|
|
||||||
headers: await _headers(),
|
|
||||||
body: jsonEncode({'couple_id': coupleId}),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
|
||||||
throw Exception(
|
|
||||||
_extractError(response.body, 'Erreur sélection du couple de garde (AM)'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final decoded = jsonDecode(response.body);
|
|
||||||
return CouplesGardeResponse.fromJson(
|
|
||||||
Map<String, dynamic>.from(decoded as Map),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class CoupleSelectorBandeau extends StatelessWidget {
|
|||||||
|
|
||||||
CoupleGarde? get _selected {
|
CoupleGarde? get _selected {
|
||||||
if (couples.isEmpty) return null;
|
if (couples.isEmpty) return null;
|
||||||
if (selectedCoupleId != null) {
|
if (selectedCoupleId != null) {
|
||||||
for (final c in couples) {
|
for (final c in couples) {
|
||||||
if (c.id == selectedCoupleId) return c;
|
if (c.id == selectedCoupleId) return c;
|
||||||
}
|
}
|
||||||
@@ -133,47 +133,8 @@ class _CoupleCard extends StatelessWidget {
|
|||||||
required this.colorIndex,
|
required this.colorIndex,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Libellé parents côté AM : « Sophie » ou « Thomas et Claire ».
|
|
||||||
static String _parentsLabel(List<CoupleMembre> parents) {
|
|
||||||
if (parents.isEmpty) return 'Parents';
|
|
||||||
String prenomOf(CoupleMembre p) {
|
|
||||||
final prenom = (p.prenom ?? '').trim();
|
|
||||||
if (prenom.isNotEmpty) return prenom;
|
|
||||||
return p.displayName(fallback: 'Parent');
|
|
||||||
}
|
|
||||||
if (parents.length == 1) return prenomOf(parents.first);
|
|
||||||
return '${prenomOf(parents[0])} et ${prenomOf(parents[1])}';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// Parent mode: enfant | AM (photo + prénom)
|
|
||||||
// AM mode: enfant | parent(s) — texte seul, pas de vignette parent
|
|
||||||
final isParentMode = mode == CoupleBandeauMode.parent;
|
|
||||||
|
|
||||||
Widget amOrParentsWidget;
|
|
||||||
|
|
||||||
if (isParentMode) {
|
|
||||||
amOrParentsWidget = _MembreTile(
|
|
||||||
photoUrl: couple.am?.photoUrl,
|
|
||||||
name: (couple.am?.prenom != null && couple.am!.prenom!.isNotEmpty)
|
|
||||||
? couple.am!.prenom!
|
|
||||||
: (couple.am?.displayName(fallback: 'Nounou') ?? 'Nounou'),
|
|
||||||
fallbackIcon: Icons.volunteer_activism,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
amOrParentsWidget = Text(
|
|
||||||
_parentsLabel(couple.parents),
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: GoogleFonts.merienda(
|
|
||||||
fontSize: 22,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: QuotidienTheme.ink,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
height: 90,
|
height: 90,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@@ -210,7 +171,19 @@ class _CoupleCard extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(left: 12),
|
padding: const EdgeInsets.only(left: 12),
|
||||||
child: amOrParentsWidget,
|
child: _MembreTile(
|
||||||
|
photoUrl: couple.am.photoUrl,
|
||||||
|
name: (couple.am.prenom != null && couple.am.prenom!.isNotEmpty)
|
||||||
|
? couple.am.prenom!
|
||||||
|
: couple.am.displayName(
|
||||||
|
fallback: mode == CoupleBandeauMode.parent
|
||||||
|
? 'Nounou'
|
||||||
|
: 'Parent',
|
||||||
|
),
|
||||||
|
fallbackIcon: mode == CoupleBandeauMode.parent
|
||||||
|
? Icons.volunteer_activism
|
||||||
|
: Icons.person_outline,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
|||||||
Reference in New Issue
Block a user