Skip to content
Open
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
37 changes: 37 additions & 0 deletions civictechprojects/static/css/partials/_VARSelectWeek.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
.VARSelectWeek-wrapper {
display: flex;
flex-direction: column;
gap: 0.25rem;
}

.VARSelectWeek-label {
font-weight: 600;
font-size: 1rem;
}

.required {
color: red;
}

.VARSelectWeek-dropdown {
height: 40px;
border-radius: 4px;
border: 1px solid #cbd5e1;
padding: 0 0.5rem;
font-size: 1rem;
background-color: #fff;
}

.VARSelectWeek-dropdown.error {
border-color: #dc2626;
}

.VARSelectWeek-helper {
font-size: 0.875rem;
color: #6b7280;
}

.VARSelectWeek-error {
font-size: 0.875rem;
color: #dc2626;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
.VARCardIntro-wrapper {
display: flex;
flex-direction: column;
gap: 0.5rem;
}

.VARCardIntro-header {
display: flex;
justify-content: space-between;
align-items: center;
}

.VARCardIntro-project {
font-size: 1rem;
font-weight: 600;
}

/* Toggle */
.VARCardIntro-toggle {
position: relative;
display: inline-block;
width: 36px;
height: 20px;
}

.VARCardIntro-toggle input {
opacity: 0;
width: 0;
height: 0;
}

.VARCardIntro-slider {
position: absolute;
cursor: pointer;
inset: 0;
background-color: #ccc;
border-radius: 999px;
transition: background-color 0.2s;
}

.VARCardIntro-slider::before {
content: '';
position: absolute;
height: 14px;
width: 14px;
left: 3px;
top: 3px;
background-color: #fff;
border-radius: 50%;
transition: transform 0.2s;
}

.VARCardIntro-toggle input:checked + .VARCardIntro-slider {
background-color: #16a34a;
}

.VARCardIntro-toggle input:checked + .VARCardIntro-slider::before {
transform: translateX(16px);
}

.VARCardIntro-message {
font-size: 0.875rem;
color: #6b7280;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
.VARQ2-wrapper {
font-family: Arial, sans-serif;
padding: 1rem;
max-width: 600px;
margin: 0 auto;
}

.VARQ2-label {
display: block;
margin-bottom: 0.5rem;
font-size: 1rem;
font-weight: bold;
}

.VARQ2-input-container {
position: relative;
border: 1px solid #ccc;
border-radius: 4px;
padding: 0.5rem;
transition: border-color 0.2s ease-in-out;
background-color: #fff;
}

.VARQ2-input-container.over-limit {
border-color: red;
}

.VARQ2-input {
width: 100%;
border: none;
resize: vertical;
min-height: 40px;
font-size: 1rem;
outline: none;
padding-bottom: 1.5rem;
overflow: hidden;
}

/* Focus handled via CSS — no JS state */
.VARQ2-input:focus {
outline: none;
}

.VARQ2-input::placeholder {
color: #aaa;
}

.VARQ2-counter-wrapper {
position: absolute;
bottom: 0.25rem;
right: 0.5rem;
font-size: 0.75rem;
color: #666;
}

.VARQ2-char-count.error-color {
color: red;
}

.VARQ2-error-message {
color: red;
font-size: 0.875rem;
margin-top: 0.25rem;
}

@media (min-width: 768px) {
.VARQ2-wrapper {
max-width: 900px;
}
}
5 changes: 4 additions & 1 deletion civictechprojects/static/css/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,7 @@
@import "partials/VARFormTitle";
@import "partials/VARFormDivider";
@import "partials/VARErrorNotification";
@import "partials/VARSubmitButton";
@import "partials/VARSubmitButton";
@import "partials/VolunteerActivityReportingCardIntro";
@import "partials/VARSelectWeek";
@import "partials/VolunteerActivityReportingQ2";
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React from 'react';
import PropTypes from 'prop-types';
import { format, startOfWeek, endOfWeek, subWeeks } from 'date-fns';

/**
* Helper to generate recent week ranges
*/
const generateWeeks = (count = 8) => {
const weeks = [];
const today = new Date();

for (let i = 0; i < count; i += 1) {
const start = startOfWeek(subWeeks(today, i), { weekStartsOn: 1 });
const end = endOfWeek(start, { weekStartsOn: 1 });

weeks.push({
label: `${format(start, 'MMMM d')} – ${format(end, 'MMMM d, yyyy')}`,
startDate: format(start, 'yyyy-MM-dd'),
endDate: format(end, 'yyyy-MM-dd'),
});
}

return weeks;
};

const VARSelectWeek = ({ selectedWeek, onUpdate, errorMessage }) => {
const weeks = generateWeeks();

const handleChange = (e) => {
const week = weeks.find(w => w.startDate === e.target.value);
if (week && onUpdate) {
onUpdate(week);
}
};

return (
<div className="VARSelectWeek-wrapper">
<label className="VARSelectWeek-label">
Logging Activity for Week of: <span className="required">*</span>
</label>

<select
className={`VARSelectWeek-dropdown ${errorMessage ? 'error' : ''}`}
value={selectedWeek?.startDate || ''}
onChange={handleChange}
>
<option value="" disabled>
Select a week
</option>
{weeks.map((week) => (
<option key={week.startDate} value={week.startDate}>
{week.label}
</option>
))}
</select>

{!errorMessage && (
<div className="VARSelectWeek-helper">
Use dropdown to select an earlier week
</div>
)}

{errorMessage && (
<div className="VARSelectWeek-error">
{errorMessage}
</div>
)}

</div>
);
};

VARSelectWeek.propTypes = {
selectedWeek: PropTypes.shape({
label: PropTypes.string.isRequired,
startDate: PropTypes.string.isRequired,
endDate: PropTypes.string.isRequired,
}),
onUpdate: PropTypes.func,
errorMessage: PropTypes.string,
};

export default VARSelectWeek;
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';

const VolunteerActivityReportingCardIntro = ({
className = '',
style,
projectName,
logActivity: propLogActivity = true,
onUpdate,
}) => {
const [logActivity, setLogActivity] = useState(propLogActivity);

// Sync state if parent updates logActivity (e.g. fetched data)
useEffect(() => {
setLogActivity(propLogActivity);
}, [propLogActivity]);

const handleToggle = () => {
const updatedValue = !logActivity;
setLogActivity(updatedValue);

if (onUpdate) {
onUpdate({ logActivity: updatedValue });
}
};

return (
<div
className={`VARCardIntro-wrapper ${className}`}
style={style}
>
<div className="VARCardIntro-header">
<span className="VARCardIntro-project">
Project: {projectName}
</span>

<label className="VARCardIntro-toggle">
<input
type="checkbox"
checked={logActivity}
onChange={handleToggle}
/>
<span className="VARCardIntro-slider" />
</label>
</div>

<div className="VARCardIntro-message">
{logActivity
? 'Log activity for this project'
: 'No activity to log'}
</div>
</div>
);
};

VolunteerActivityReportingCardIntro.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
projectName: PropTypes.string.isRequired,
logActivity: PropTypes.bool,
onUpdate: PropTypes.func,
};

export default VolunteerActivityReportingCardIntro;
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';

const VolunteerActivityReportingQ2 = ({ className = '', value: propValue = '' }) => {
const [value, setValue] = useState(propValue);

// Keep internal state in sync if parent value changes (e.g. fetched data)
useEffect(() => {
setValue(propValue);
}, [propValue]);

const charCount = value.length;
const isOverLimit = charCount > 150;

const handleChange = (e) => {
setValue(e.target.value);
};

return (
<div className={`VARQ2-wrapper ${className}`}>
<label htmlFor="volunteer-activity-summary" className="VARQ2-label">
In a few words, describe what you did during the week.
</label>

<div className={`VARQ2-input-container ${isOverLimit ? 'over-limit' : ''}`}>
<textarea
id="volunteer-activity-summary"
name="volunteer_activity_summary"
className="VARQ2-input"
value={value}
onChange={handleChange}
placeholder="Enter text..."
rows={1}
/>

<div className="VARQ2-counter-wrapper">
<span className={`VARQ2-char-count ${isOverLimit ? 'error-color' : ''}`}>
{charCount}/150
</span>
</div>
</div>

{isOverLimit && (
<div className="VARQ2-error-message">
Please limit your response to 150 characters.
</div>
)}
</div>
);
};

VolunteerActivityReportingQ2.propTypes = {
className: PropTypes.string,
value: PropTypes.string,
};

export default VolunteerActivityReportingQ2;
Loading