Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
71 changes: 71 additions & 0 deletions app/Imports/MatchmakingProfileImport.php
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,33 @@ protected function generateSlug($organisationName, $email): string
return $slug;
}

/**
* Normalize CSV header keys to a consistent format
*/
protected function normalizeKey(string $key): string
{
$key = str_replace("\xc2\xa0", ' ', $key); // replace non-breaking space
$key = trim($key);
$key = preg_replace('/[^\pL\pN]+/u', '_', $key);
$key = trim($key, '_');
return mb_strtolower($key);
}

/**
* Normalize row keys and merge with original keys
*/
protected function normalizeRowKeys(array $row): array
{
$normalized = $row;
foreach ($row as $key => $value) {
$normalizedKey = $this->normalizeKey((string) $key);
if (!array_key_exists($normalizedKey, $normalized)) {
$normalized[$normalizedKey] = $value;
}
}
return $normalized;
}

/**
* Get value from row by trying multiple possible key names
*/
Expand All @@ -221,6 +248,10 @@ protected function getRowValue(array $row, array $possibleKeys, $default = null)
if (isset($row[$key]) && !empty($row[$key])) {
return $row[$key];
}
$normalizedKey = $this->normalizeKey((string) $key);
if (isset($row[$normalizedKey]) && !empty($row[$normalizedKey])) {
return $row[$normalizedKey];
}
}
return $default;
}
Expand All @@ -231,6 +262,8 @@ protected function getRowValue(array $row, array $possibleKeys, $default = null)
public function model(array $row): ?Model
{
// Normalize row keys to handle various CSV header formats
$row = $this->normalizeRowKeys($row);

// Try to get email and organisation name with multiple possible key variations
$email = $this->getRowValue($row, [
'email',
Expand All @@ -246,6 +279,28 @@ public function model(array $row): ?Model
'Organization name',
]);

// Name fields (volunteers)
$fullName = $this->getRowValue($row, [
'name',
'Name',
]);
$firstName = $this->getRowValue($row, [
'first_name',
'First Name',
]);
$lastName = $this->getRowValue($row, [
'last_name',
'Last Name',
]);
$jobTitle = $this->getRowValue($row, [
'job_title',
'Job Title',
]);
$mainEmailAddress = $this->getRowValue($row, [
'main_email_address',
'Main email address',
]);

// Skip rows without essential data
if (empty($email) && empty($organisationName)) {
Log::warning('[MatchmakingProfileImport] Skipping row - missing email and organisation_name', $row);
Expand All @@ -255,11 +310,23 @@ public function model(array $row): ?Model
// Trim values
$email = $email ? trim($email) : null;
$organisationName = $organisationName ? trim($organisationName) : null;
$fullName = $fullName ? trim($fullName) : null;
$firstName = $firstName ? trim($firstName) : null;
$lastName = $lastName ? trim($lastName) : null;
$jobTitle = $jobTitle ? trim($jobTitle) : null;
$mainEmailAddress = $mainEmailAddress ? trim($mainEmailAddress) : null;

// Determine type - if organisation_name exists, it's an organisation, otherwise volunteer
$type = !empty($organisationName)
? MatchmakingProfile::TYPE_ORGANISATION
: MatchmakingProfile::TYPE_VOLUNTEER;

// If we only have full name, split into first/last for volunteers
if ($type === MatchmakingProfile::TYPE_VOLUNTEER && empty($firstName) && empty($lastName) && !empty($fullName)) {
$parts = preg_split('/\s+/', $fullName);
$firstName = array_shift($parts);
$lastName = count($parts) ? implode(' ', $parts) : null;
}

Log::info('[MatchmakingProfileImport] Processing row', [
'type' => $type,
Expand Down Expand Up @@ -485,6 +552,10 @@ public function model(array $row): ?Model
$profileData = [
'type' => $type,
'email' => $email,
'first_name' => $firstName,
'last_name' => $lastName,
'job_title' => $jobTitle,
'get_email_from' => $mainEmailAddress,
'organisation_name' => $organisationName,
'organisation_type' => $organisationType,
'organisation_mission' => $organisationMission,
Expand Down
97 changes: 58 additions & 39 deletions app/Nova/MatchmakingProfile.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,84 +29,103 @@ class MatchmakingProfile extends Resource
public function fields(Request $request)
{
return [
// Index fields (ordered)
ID::make()->sortable(),

Select::make('Type')
->options(array_combine(MatchmakingProfileModel::getValidTypes(), MatchmakingProfileModel::getValidTypes()))
->displayUsingLabels()
->sortable(),

Text::make('Organisation Name')->sortable(),
Text::make('Email')->sortable(),
Text::make('Name', function () {
return $this->display_name;
})->onlyOnIndex(),
DateTime::make('Start Time')->sortable(),
DateTime::make('Completion Time')->sortable(),
DateTime::make('Last Modified Time', 'updated_at')->sortable(),
Text::make('Organisation Website', 'website')->onlyOnIndex(),
Text::make('Organisation Type', function () {
return is_array($this->organisation_type) ? implode(', ', $this->organisation_type) : $this->organisation_type;
})->onlyOnIndex(),
Text::make('Main Email Address', 'get_email_from')->onlyOnIndex(),
Text::make('Country/Region/Areas of operation', function () {
return $this->countryModel ? $this->countryModel->name : $this->country;
})->onlyOnIndex(),
Text::make('Want to tell us more about your organisation?', 'organisation_mission')->onlyOnIndex(),
Boolean::make('Do you give your consent to use your logo and display it in the matchmaking directory?', 'is_use_resource')->onlyOnIndex(),
Text::make('What kind of activities or support can you offer to schools and educators? (Select all that apply):', function () {
return is_array($this->support_activities) ? implode(', ', $this->support_activities) : $this->support_activities;
})->onlyOnIndex(),
Text::make('Are you interested in collaborating with schools to bring real-world expertise into the classroom?', 'interested_in_school_collaboration')->onlyOnIndex(),
Text::make('What types of schools are you most interested in working with?', function () {
return is_array($this->target_school_types) ? implode(', ', $this->target_school_types) : $this->target_school_types;
})->onlyOnIndex(),
Text::make('What areas of digital expertise does your organisation or you specialise in?', function () {
return is_array($this->digital_expertise_areas) ? implode(', ', $this->digital_expertise_areas) : $this->digital_expertise_areas;
})->onlyOnIndex(),
Boolean::make('Would you like to be part of the wider EU Code Week community and receive updates about future activities and events?', 'want_updates')->onlyOnIndex(),
Text::make('Do you have any additional information or comments that could help us better match you with schools and educators?', 'description')->onlyOnIndex(),
Boolean::make('By registering as a Digital Volunteer, you agree to being contacted later to share feedback about your experience.', 'agree_to_be_contacted_for_feedback')->onlyOnIndex(),

// Detail fields
Text::make('Slug')
->sortable()
->rules('required', 'max:255')
->hideFromIndex(),

Text::make('Avatar')->hideFromIndex(),

Text::make('Email')->sortable(),
Text::make('First Name')->sortable(),
Text::make('Last Name')->sortable(),
Text::make('Job Title'),
Text::make('First Name')->hideFromIndex(),
Text::make('Last Name')->hideFromIndex(),
Text::make('Job Title')->hideFromIndex(),
Text::make('Linkedin')->hideFromIndex(),
Text::make('Facebook')->hideFromIndex(),
Text::make('Website'),
Text::make('Organisation Name')->sortable(),
Text::make('Website')->hideFromIndex(),

// Array fields as JSON textareas
Textarea::make('Languages')
->resolveUsing(fn($v) => is_array($v) ? implode(', ', $v) : ($v ?: ''))
->fillUsing(fn($req, $mdl, $attr, $reqAttr) => $mdl->{$attr} = json_decode($req->{$reqAttr}, true))
->alwaysShow(),
->hideFromIndex(),

Textarea::make('Organisation Type')
->resolveUsing(fn($v) => is_array($v) ? implode(', ', $v) : ($v ?: ''))
->fillUsing(fn($req, $mdl, $attr, $reqAttr) => $mdl->{$attr} = json_decode($req->{$reqAttr}, true))
->alwaysShow(),

Textarea::make('Organisation Mission')->alwaysShow(),
->hideFromIndex(),

Text::make('Location'),
Textarea::make('Organisation Mission')->hideFromIndex(),
Text::make('Location')->hideFromIndex(),
BelongsTo::make('Country', 'countryModel', \App\Nova\Country::class)
->nullable()
->sortable()
->searchable(),
->searchable()
->hideFromIndex(),

Textarea::make('Support Activities')
->resolveUsing(fn($v) => is_array($v) ? implode(', ', $v) : ($v ?: ''))
->fillUsing(fn($req, $mdl, $attr, $reqAttr) => $mdl->{$attr} = json_decode($req->{$reqAttr}, true))
->alwaysShow(),

Text::make('Interested In School Collaboration')->sortable(),
->hideFromIndex(),

Text::make('Interested In School Collaboration')->hideFromIndex(),
Textarea::make('Target School Types')
->resolveUsing(fn($v) => is_array($v) ? implode(', ', $v) : ($v ?: ''))
->fillUsing(fn($req, $mdl, $attr, $reqAttr) => $mdl->{$attr} = json_decode($req->{$reqAttr}, true))
->alwaysShow(),

->hideFromIndex(),
Textarea::make('Time Commitment')
->resolveUsing(fn($v) => is_array($v) ? implode(', ', $v) : ($v ?: ''))
->fillUsing(fn($req, $mdl, $attr, $reqAttr) => $mdl->{$attr} = json_decode($req->{$reqAttr}, true))
->alwaysShow(),

->hideFromIndex(),
Boolean::make('Dark Avatar', 'avatar_dark')->hideFromIndex(),
Boolean::make('Can Start Immediately'),
Textarea::make('Why Volunteering')->alwaysShow(),

Boolean::make('Can Start Immediately')->hideFromIndex(),
Textarea::make('Why Volunteering')->hideFromIndex(),
Select::make('Format')
->options(MatchmakingProfileModel::getValidFormats())
->displayUsingLabels()
->sortable(),

Boolean::make('Is Use Resource'),
Boolean::make('Want Updates'),
Boolean::make('Agree To Be Contacted For Feedback'),
Textarea::make('Description')->alwaysShow(),

DateTime::make('Start Time')->sortable(),
DateTime::make('Completion Time')->sortable(),

Boolean::make('Email Via Linkedin'),
Text::make('Get Email From'),
->sortable()
->hideFromIndex(),
Boolean::make('Is Use Resource')->hideFromIndex(),
Boolean::make('Want Updates')->hideFromIndex(),
Boolean::make('Agree To Be Contacted For Feedback')->hideFromIndex(),
Textarea::make('Description')->hideFromIndex(),
Boolean::make('Email Via Linkedin')->hideFromIndex(),
Text::make('Get Email From')->hideFromIndex(),
];
}

Expand Down