DEV Community

SEN LLC
SEN LLC

Posted on

A Travel Checklist Generator With Conditional Items and Trip Profiles

A Travel Checklist Generator With Conditional Items and Trip Profiles

Tell the app you're going to Okinawa for 10 days in summer. It gives you a list that includes swimsuit, sunscreen, laundry bag (because 10+ days), no winter coat, no passport (domestic). Change the trip to Paris for 5 days and the list now includes passport, travel adapter, and winter coat. 107 items with conditional logic, all driven by a declarative rule language.

Every trip checklist article on the internet is the same: a generic list with "the 50 items you should never forget." But the items you need depend entirely on the trip. A business trip needs laptop + cables but not a swimsuit. A beach trip needs sunscreen but not dress shirts. Conditional logic makes one list serve every trip.

πŸ”— Live demo: https://sen.ltd/portfolio/travel-checklist/
πŸ“¦ GitHub: https://github.com/sen-ltd/travel-checklist

Screenshot

Features:

  • 107 items with conditions
  • 7 categories (Documents, Clothing, Toiletries, Electronics, Health, Entertainment, Misc)
  • Trip profile inputs: destination, season, duration, transport, trip type
  • Saved trip profiles in localStorage
  • Add custom items
  • Print-friendly layout
  • Japanese / English UI
  • Zero dependencies, 37 tests

The item database

Each item has a name, category, priority, and conditions:

{
  id: 'passport',
  category: 'documents',
  name: { ja: 'γƒ‘γ‚ΉγƒγƒΌγƒˆ', en: 'Passport' },
  conditions: { destination: ['international'] },
  priority: 'critical',
},
{
  id: 'swimsuit',
  category: 'clothing',
  name: { ja: '水着', en: 'Swimsuit' },
  conditions: { tripType: ['beach'] },
  priority: 'high',
},
{
  id: 'laundry-bag',
  category: 'misc',
  name: { ja: 'ζ΄—ζΏ―θ’‹', en: 'Laundry bag' },
  conditions: { minDays: 7 },
  priority: 'low',
},
Enter fullscreen mode Exit fullscreen mode

Conditions can be:

  • Array match: destination: ['international'] β€” match if profile is in list
  • Numeric threshold: minDays: 7 β€” match if duration β‰₯ 7
  • Logic: conditionLogic: 'any' β€” OR instead of the default AND

The matcher

export function matchesConditions(item, profile) {
  const cond = item.conditions || {};
  const keys = Object.keys(cond);
  if (keys.length === 0) return true; // always needed

  const checks = keys.filter(k => k !== 'conditionLogic').map(key => {
    if (key === 'minDays') return profile.days >= cond.minDays;
    if (key === 'maxDays') return profile.days <= cond.maxDays;
    if (Array.isArray(cond[key])) {
      return cond[key].includes(profile[key]);
    }
    return false;
  });

  return cond.conditionLogic === 'any'
    ? checks.some(Boolean)
    : checks.every(Boolean);
}
Enter fullscreen mode Exit fullscreen mode

Default is AND logic: all conditions must match. conditionLogic: 'any' switches to OR β€” useful for items like sunscreen that apply to summer OR beach trips regardless of season.

Sunscreen as an OR case

{
  id: 'sunscreen',
  conditions: {
    season: ['summer'],
    tripType: ['beach', 'adventure'],
    conditionLogic: 'any',
  },
}
Enter fullscreen mode Exit fullscreen mode

Match if: summer season, OR beach trip, OR adventure trip. All three mean "you'll be in the sun."

Without OR logic, you'd need three separate sunscreen entries. With it, one entry covers all the cases.

Why declarative conditions

The alternative is hardcoding: if (profile.destination === 'international') items.push(passport). That works for 10 items. At 100 items you have an unmaintainable mess of if-statements.

Declarative conditions keep the rules next to the data. Adding a new item means adding a row to ITEMS β€” no code changes. Adding a new condition type (e.g., region: ['tropical']) means one switch case in the matcher. The complexity grows linearly, not quadratically.

Series

This is entry #69 in my 100+ public portfolio series.

Top comments (0)